Так что, если вы занимаетесь веб-разработкой и не живете в подвале, вы, вероятно, уже слышали о Redux.

что такое Redux?

Redux — это реализация, полученная из Flux. Это позволяет вам создать хранилище, которое содержит состояние, реагирует на отправленные действия и на которое вы можете подписаться, чтобы получать уведомления об изменениях. Это также позволяет добавлять промежуточное программное обеспечение, которое может выполнять предварительную обработку действий.

Итак, мы собираемся написать реализацию Redux, чтобы лучше понять, как она работает.

1. Управление состоянием

Redux отличается своим механизмом обновления состояния, так как он описан в редьюсерах. Вы знаете Array.prototype.reduce? Ну, ваш редуктор имеет точно такую ​​же сигнатуру, как и функция сокращения.

Проще говоря, ваше состояние всегда будет стоить:

dispatchedActions.reduce(reducer, undefined);

Если это звучит для вас немного абстрактно, давайте возьмем очень наивный пример редуктора:

// we start from 0
const initialState = { counter: 0 };

const reducer = (state = initialState, action) => {
  switch (action.type) {
    // depending on the action …
    case "INCREMENT":
      // … we return a new incremented state
      return { counter: state.counter + 1 };
    case "DECREMENT":
      // … or decrement
      return { counter: state.counter - 1 };
    default:
      // or the current state, if left untouched
      return state;
  }
};

Маленький state = initialStateэто параметр со значением по умолчанию, если вы видели кусок кода со значением reduce чуть выше, мы передаем значение undefinedкак начальный аккумулятор, это позволяет иметь действительное состояние initialStateпри первом действии.

switch позволяет вернуть новое состояние в соответствии с прошлыми действиями, с одним default, который возвращает текущее состояние, на случай, если нам наплевать на действие в этом редюсере.

Приступим к реализации:

const createStore = reducer => {
  // we balance a first "opaque" action,
  // which will not be processed by the reducer, just to start with a state
  let state = reducer(undefined, { type: "@@INIT" });
  return {
    // a method to dispatch the actions
    dispatch: action => {
      state = reducer(state, action);
    },
    // a method to retrieve the state
    getState: () => state,
  };
};

Отлично, мы можем написать и прочитать наш отчет.

2. Механизм подписки

Чтобы иметь возможность уведомлять заинтересованные стороны о том, что в состоянии произошли обновления, мы должны добавить механизм подписки: генератор событий зверя.

const createStore = reducer => {
  let state = reducer(undefined, { type: "@@INIT" });
  // we create a `Set` where we will store the listeners
  const subscribers = new Set();
  return {
    dispatch: action => {
      state = reducer(state, action);
      // at each dispatch, we call the subscribers
      subscribers.forEach(func => func());
    },
    subscribe: func => {
      // we add `func` to the subscribers list
      subscribers.add(func);
      // and we return a function allowing to unsubscribe
      return () => {
        subscribers.delete(func);
      };
    },
    getState: () => state,
  };
};

Да, механизм на месте.

3. Комбинируйте переходники

Теперь было бы неплохо разрешить иметь несколько редюсеров, чтобы иметь возможность их нарезать и избежать большого раздутого кода, который пачкает. Для этого мы создадим функцию combineReducers, которая возьмет объект, содержащий редукторы, и преобразует его в один редьюсер, который вернет объект той же формы, с состоянием, возвращаемым редьюсером того же ключа.

const combineReducers = reducers => {
  const reducersKeys = Object.keys(reducers);
  return (state = {}, action) => {
    return reducersKeys.reduce((acc, key) => {
      acc[key] = reducers[key](state[key], action);
      return acc;
    }, {});
  };
};

Теперь мы можем разделить наши редукторы:

import { users } from "./reducers/user";
import { tweets } from "./reducers/tweets";

const reducer = combineReducers({
  users,
  tweets,
});

const store = createStore(reducer);

4. Разрешить добавление промежуточного программного обеспечения

Промежуточное ПО дает полную свободу настройки Redux. Одним из самых популярных является thunk промежуточного слоя, который позволяет вам передавать функцию вместо действия и выполнять диспетчеризацию из этой функции, что может быть очень полезно для обработки асинхронных ответов.

Поэтому мы собираемся создать функцию applyMiddleware, которая обогатит хранилище, добавив своего рода хук в его метод отправки.

// compose(a, b, c) is equivalent to (...args) => a(b(c(...args)))
const compose = (...funcs) => {
  const last = funcs[funcs.length - 1];
  const rest = funcs.slice(0, -1);
  return (...args) =>
    rest.reduceRight((composed, f) => f(composed), last(...args));
};

const applyMiddleware = (...middlewares) => {
  return store => {
    // this API will be passed to each middleware, so that it can retrieve
    // the current state and dispatch actions
    const middlewareAPI = {
      getState: store.getState,
      dispatch: action => dispatch(action),
    };
    const chain = middlewares.map(middleware => middleware(middlewareAPI));
    let dispatch = compose(...chain)(store.dispatch);
    return {
      ...store,
      dispatch,
    };
  };
};

ПРИМЕЧАНИЕ. Redux API принимает в качестве параметра createStore, а не store, разница является добровольной в этом процессе упрощения.

Ну вот, теперь мы можем сделать

const thunk = ({ dispatch, getState }) => next => action => {
  if (typeof action === "function") {
    return action(dispatch, getState);
  }
  return next(action);
};

const store = applyMiddleware(thunk)(createStore(reducer));

// and now we can do
store.dispatch((dispatch, getState) => {
  dispatch({ type: "FOO" });
  setTimeout(() => {
    dispatch({ type: "BAR" });
  });
});

Ну вот, я надеюсь, что эта статья смогла немного просветить вас о функционировании Redux и позволит вам подойти к нему с большим спокойствием.