В этом блоге я попытаюсь объяснить библиотеку redux
без react. Просто Redux.
Почему? Потому что если мы поймем Redux, то мы лучше поймем react-redux
(привязка Redux к react).
Итак, Redux — это относительно очень маленький API.
compose
createStore
bindActionCreators
combineReducers
applyMiddleware
Да, только эти пять функций или методов. Мы разберем каждую по отдельности.
Начнем с compose.
compose
Это просто утилита, которая поставляется вместе с Redux. Это просто функция javaScript, которая принимает функции в качестве аргументов и возвращает новую функцию, которая выполняет их справа налево. Вот и все.
Чтобы лучше понять это, допустим, у нас есть куча функций, которые принимают строку
const makeUppercase = (string) => string.toUpperCase();
const logString = (string) => console.log(string)
const boldString = (string) => string.bold();
Мы можем назвать их все следующим образом:
logString(makeUppercase(boldString("redux")));
Но, допустим, если мы захотим передать эту функцию в качестве аргумента другой функции, что мы сделаем?
const boldenTheStringAndUppcaseItThenLogIt = (string) =>
logString(makeUppercase(boldString(string)));
Это может занять слишком много времени, поэтому мы compose
. Это позволяет создать новую функцию, которая будет выполнять все функции, переданные в качестве аргументов в compose(args)
.
const boldenTheStringAndUppcaseItThenLogIt = redux.compose(
logString , makeUppercase, boldString
)
boldenTheStringAndUppcaseItThenLogIt("")
это выполнит функции от справа налево
(boldString → makeUppercase → logString) как «redux» в качестве аргумента sting, и мы закончим с compose.
Вот что такое compose
.
До сих пор мы рассмотрели 20% для redux API
.
Теперь давайте разберемся с createStore
. Create store создает магазин. Магазин — это место, где мы храним все наши данные.
Но это не просто создание магазина, однако
// reducer is a function that we need to pass to the createStore,
// we will cover what a reducer shortly..
let store = redux.createStore(reducer)
console.log(store)
// dispatch: ƒ dispatch() {}
// subscribe: ƒ subscribe() {}
// getState: ƒ getState() {}
// replaceReducer: ƒ replaceReducer() {}
еще четыре функции
dispatch
subscribe
getState
replaceReducer
Но что такое редуктор?
Редуктор — это функция, которая принимает два аргумента
- состояние (состояние приложения)
- действие (действие — это событие, например, сообщения веб-сокета, функции действия и т.д.)
и возвращает новое состояние.
Итак, по сути, reducer — это функция, где первый аргумент - текущее состояние приложения
, а второй - то, что произошло
. Где-то внутри функции мы выясняем, каким должно быть новое состояние мира, исходя из того, что произошло.
// this is not how you should write a reducer , just for example
const reducer = (state, action) => state //(new state)
Давайте попробуем понять это на примере. Допустим, у нас есть приложение-счетчик, и мы хотим увеличить счетчик, у нас будет действие increment.
Теперь, действия также являются просто функциями, и они должны иметь только один тип,
const initialState = { value: 0 };
const reducer = (state = initialState, action) => {
if (action.type === "INCREMENT") {
return { value: state.value + 1 };
}
return state;
};
// which can also be written as
const initialState = { value: 0 };
const INCREMENT = "INCREMENT"; //
const incrementCounterAction = { type: INCREMENT }; // only requires type ,
// others are optional
// payload : {}, meta: {}, error.
const reducer = (state = initialState, action) => {
if (action.type === INCREMENT) {
return { value: state.value + 1 };
}
return state;
};
Хорошо, здесь есть несколько моментов, которые нам нужно понять.
- Вы заметите, что мы создаем новый объект, а не изменяем существующий.
- Это полезно, поскольку позволяет нам определить новое состояние в зависимости от текущего состояния.
- Мы также хотим быть уверены, что вернем существующее состояние, если через редуктор пройдет действие, о котором мы не заботимся.
Вы также заметите, что мы создали константу INCREMENT
. Основная причина, по которой мы это делаем, заключается в том, что нам нужно убедиться, что мы случайно не ошибемся в написании типа действия — ни при создании действия, ни в редукторе.
Допустим, у нас есть счетчик, который имеет кнопку увеличения и вход add, который увеличивает счетчик и добавляет к нему значение, которое мы вводим во вход.
const initialState = { value: 0 }; // state of the application
const INCREMENT = "INCREMENT"; // constants
const ADD = "ADD"; // constants
// action creators (fancy name for functions)
const increment = () => ({ type: INCREMENT });
const add = (number) => ({ type: ADD, payload: number });
const reducer = (state = initialState, action) => {
if (action.type === INCREMENT) {
return { value: state.value + 1 }; // new state
}
if (action.type === ADD) {
return { value: state.value + action.payload }; // new state
}
return state; // default state
};
До сих пор мы понимали, что такое compose, что делает createStore и четыре функции, которые он создает, что такое reducer и action. Но нам нужно еще понять, какие четыре функции создает createStore.
Поэтому начнем с
const store = createStore(reducer);
console.log(store.getState()); // { value: 0 }
store.dispatch(increment());
console.log(store.getState()); // { value: 1 }
const subscriber = () => console.log('this is a subscriber!' store.getState().value);
const unsubscribe = store.subscribe(subscriber);
store.dispatch(increment()); // "Subscriber! 1"
store.dispatch(add(4)); // "Subscriber! 5"
unsubscribe();
bindActionCreators
Ранее в блоге мы читали об экшенах (которые являются функциями). Угадайте, что делает bindActionCreators? … связывает функции действий вместе!!!
В примере ниже у нас есть экшены и редукторы.
const initialState = { value: 0 }; // state of the application
const INCREMENT = "INCREMENT"; // constants
const ADD = "ADD"; // constants
// action creators (fancy name for functions)
const increment = () => ({ type: INCREMENT });
const add = (number) => ({ type: ADD, payload: number });
const reducer = (state = initialState, action) => {
if (action.type === INCREMENT) {
return { value: state.value + 1 }; // new state
}
if (action.type === ADD) {
return { value: state.value + action.payload }; // new state
}
return state; // default state
};
-----------------------------------------------------------------------------
const store = createStore(reducer);
/// notice we have to do this like this everytime
store.dispatch(increment());
Обратите внимание, что мы должны диспетчеризировать действия store.dispatch(increment())
каждый раз, это может быть утомительно в большом приложении.
Мы могли бы сделать это более чистым способом, например так
const dispatchIncrement = () => store.dispatch(increment());
const dispatchAdd = (number) => store.dispatch(add(number));
dispatchIncrement();
dispatchAdd();
или мы можем использовать compose
. Помните, как мы писали о compose ранее в этом блоге?
const dispatchIncrement = compose(store.dispatch, increment);
const dispatchAdd = compose(store.dispatch, add);
Мы также можем сделать это с помощью bindActionCreators
, принимает два аргумента
- создатели действий (функции действий)
- отправлять
const actions = bindActionCreators(
{
increment,
add,
},
store.dispatch
);
actions.increment();
Теперь нам не нужно постоянно использовать bindActionCreators, но он есть.
На этом мы завершили работу над 60% API redux.
combineReducers
Угадайте, что это делает? Да, объединяет редукторы. Когда у нас большие приложения, у нас много редукторов.
Например, когда у нас есть приложение для ведения блога. У нас есть reducers для хранения информации о пользователе, reducers для хранения блогов, reducers для хранения комментариев.
В таких больших приложениях мы разделяем редукторы на разные файлы. combineReducers
используется, когда у нас есть несколько редукторов и мы хотим их объединить.
Скажем, у нас есть приложение с пользователями и задачами. Мы можем назначать пользователей и ставить задачи
const initState = {
users: [
{ id: 1, name: "ved" },
{ id: 2, name: "ananya" },
],
tasks: [
{ title: "some task", assignedTo: 1 },
{ title: "another task", assignedTo: null },
],
};
файл редуктора
const ADD_USER = "ADD_USER";
const ADD_TASK = "ADD_TASK";
const addTask = title => ({ type: ADD_TASK, payload: { title } });
const addUser = name => ({ type: ADD_USER, payload: { name } });
const reducer = (state = initialState, action) => {
if (action.type === ADD_USER) {
return {
...state,
users: [...state.users, action.payload],
};
}
if (action.type === ADD_TASK) {
return {
...state,
tasks: [...state.tasks, action.payload],
};
}
};
const store = createStore(reducer, initialState);
store.dispatch(addTask("Record the song"));
store.dispatch(addUser("moksh")); // moksh is my brother's name 😃
console.log(store.getState());
Было бы хорошо, если бы мы могли иметь два разных редуктора для пользователей и задач
const users = (state = initialState.users, action) => {
if (action.type === ADD_USER) {
return [...state, action.payload];
}
return state;
};
const tasks = (state = initialState.tasks, action) => {
if (action.type === ADD_TASK) {
return [...state, action.payload];
}
return state;
};
// now we need to combine the reducers and we do that using combineReducers
const reducer = redux.combineReducers({ users, tasks });
const store = createStore(reducer, initialState);
Забавный факт! Все действия проходят через все редукторы. Поэтому, если вы хотите обновить два фрагмента состояния одним и тем же действием, вы вполне можете
applyMiddleware
Есть много вещей, которые Redux не может сделать сам по себе, поэтому мы можем расширить возможности Redux, используя промежуточное ПО и улучшатели.
Что же такое промежуточное ПО и улучшатели? Это просто функции, которые позволяют нам добавить больше функциональности в Redux.
Точнее, enhancers — это функции, которые берут копию createStore
и копию аргументов, переданных в createStore
, прежде чем передать их в createStore
. Это позволяет нам создавать библиотеки и плагины, которые добавят больше возможностей для работы магазина.
Мы видим, как улучшители используются, когда мы используем инструменты разработчика Redux и когда мы хотим отправлять асинхронные действия.
Фактический API для createStore()
createStore()
принимает один, два или три аргумента.
Давайте попробуем понять это на примере, ниже представлен улучшатель, который отслеживает время, необходимое для обновления состояния.
const monitorUpdateTimeReducerEnhancer = (createStore) => (
reducer,
initialState,
enhancer
) => {
const monitorUpdateTimeReducer = (state, action) => {
const start = performance.now();
const newState = reducer(state, action);
const end = performance.now();
const diff = round(end - start);
console.log("Reducer process time is:", diff);
return newState;
};
return createStore(monitorUpdateTimeReducer, initialState, enhancer);
};
const store = createStore(reducer, monitorReducerEnhancer);
Если это все еще не ясно, давайте рассмотрим другой пример, который регистрирует старое и новое состояние с помощью улучшителей.
const reducer = (state = { count: 1 }) => state;
const logStateReducerEnhancer = (createStore) => (
reducer,
initialState,
enhancer
) => {
const logStateReducer = (state, action) => {
console.log("old state", state, action);
const newState = reducer(state, action);
console.log("new state", newState, action);
return newState;
};
return createStore(logStateReducer, initialState, enhancer);
};
const store = createStore(
reducer,
logStateReducerEnhancer)
);
store.dispatch({ type: "old state" });
store.dispatch({ type: "new updated state" });
Хорошо, но для чего используется applyMiddleware? Он используется для создания enhancers из цепочки middleware. Что это значит, может быть какой-то код поможет лучше понять.
const enhancer = applyMiddleware(
firstMiddleware,
secondMiddleware,
thirdMiddleware
);
Итак, Middleware имеет следующий API
const someMiddleware = (store) => (next) => (action) => {
// Do stuff before the action reaches the reducer or the next piece of middleware.
next(action);
// Do stuff after the action has worked through the reducer.
};
next
— это либо следующая часть middleware, либо это store.dispatch
. Если вы не вызовете next
, вы проглотите действие, и оно никогда не попадет в редуктор.
Вот пример промежуточного программного обеспечения logState, которое мы рассматривали ранее, с использованием этой функции
const logStateMiddleware = (store) => (next) => (action) => {
console.log("State Before", store.getState(), { action });
next(action);
console.log("State After", store.getState(), { action });
};
const store = createStore(reducer, applyMiddleware(logStateMiddleware));
Давайте также посмотрим, как мы можем сделать это с monitorUpdateTime
const monitorUpdateTimeMiddleware = (store) => (next) => (action) => {
const start = performance.now();
next(action);
const end = performance.now();
const diff = Math.round(end - start);
console.log("Reducer process time is:", diff);
};
const store = createStore(reducer, applyMiddleware(monitorUpdateTimeMiddleware));
На этом мы рассмотрели все API Redux. Надеюсь, этот блог был полезен, и вы узнали что-то новое.