Всем привет!
Этой мой первый пост в дневнике, и посвящён он библиотеке Redux.
За время моего обучения на Hexlet именно эта библиотека породила кучу вопросов а-ля -'как это работает, почему это так работает ?'. Несколько дней я перечитывал теорию по Redux, но в голове не укладывалось, пока я не написал свою собственную версию Redux.
Предлагаю вам вместе со мной написать свою версию библиотеки, просто чтобы разобраться как это работает "под капотом".
В Redux ядром всего является объект store
, который возвращает нам функция createStore()
. Поэтому давайте начнём с создания этой функции.
Согласно документации createStore принимает первым аргументов функцию reducer, а вторым начальное состояние.
const createStore = (reducer, initialState) => {
let state = reducer(initialState, { type: '__INIT__' });
let subscribers = [];
return {
dispatch(action) {
state = reducer(state, action);
subscribers.forEach((cb) => cb());
},
subscribe(cb) {
subscribers = [...subscribers, cb];
},
getState() {
return state;
},
};
};
Сначала инициализируем state начальными данными.
let state = reducer(initialState, { type: '__INIT__' });
Функцию
reducer
мы напишем позже, но главное понимать, что функция возвращает измененный или неизменённый state.Функция
createStore
должна вернуть объектstore
со следующими методами:dispatch(), subscribe(), getState()
.
getState()
просто возвращает текущийstate
.
subscribe()
принимает в качестве аргументаcallback-функцию
и складывает функцию в массив.
dispatch()
принимает в качестве аргумента объект. Этот объект может иметь внутри любые поля и данные, но в обязательном порядке должен иметь полеtype
. Например{ type: 'ADD' }
. Когда вызывается методdispatch()
, тоstate
должен быть изменен черезreducer
. Reducer - это функция, внутри которой данные меняются, в зависимости от описанной внутри логики. После того, какstate
был изменен, нам нужно последовательно вызвать всеcallback-функции
, которые были добавлены в массив посредством методаsubscribe()
.
Теперь создаем функцию reducer
, которая согласно внутренней логики будет менять состояние.
const ourReducer = (state, action) => {
switch(action.type) {
case 'INC':
return state + 1;
case 'DEC':
return state - 1;
default:
return state;
}
};
Здесь все должно быть понятно. Функция принимает объект
action
с полемtype
. Например{ type: 'INC }
. В зависимости от значения поляtype
отрабатываетswitch-case
конструкция и происходит возврат состояния.
Дальше создаем объект store
, интерфейсами которого и будем манипулировать.
const store = createStore(ourReducer, 0);
Теперь попробуем вызвать методы нашего хранилища store
.
Для начала мы хотим чтобы при изменении нашего состояния в console
автоматически печаталось текущее состояние.
store.subscribe(() => console.log(store.getState()));
Теперь вернем текущее состояние
store.getState(); // -> вернётся 0, так как это начальное состояние.
Теперь изменим наше состояние методом dispatch()
, который внутри вызывает редьюсер, меняющий состояние, и вызывает последовательно все callback функции
из массива subscribers
.
store.dispatch({ type: 'INC' }); // -> в консоле будет 1
store.dispatch({ type: 'INC' }); // -> в консоле будет 2
store.dispatch({ type: 'DEC' }); // -> в консоле будет 1
Как оказалось, ничего сложного здесь нет. Надеюсь мои эксперименты кому-то помогут быстрее вникнуть в то, как работает библиотека Redux.
Ниже представлен полный код для удобства копирования в свой редактор:
const createStore = (reducer, initialState) => {
let state = reducer(initialState, { type: '__INIT__' });
let subscribers = [];
return {
dispatch(action) {
state = reducer(state, action);
subscribers.forEach((cb) => cb());
},
subscribe(cb) {
subscribers = [...subscribers, cb];
},
getState() {
return state;
},
};
};
const ourReducer = (state, action) => {
switch(action.type) {
case 'INC':
return state + 1;
case 'DEC':
return state - 1;
default:
return state;
}
};
const store = createStore(ourReducer, 0);
store.subscribe(() => console.log(store.getState()));
store.getState();
store.dispatch({ type: 'INC' });
store.dispatch({ type: 'INC' });
store.dispatch({ type: 'DEC' });