Redux — известная библиотека, она используется во многих местах, я ей пользуюсь несколько лет, сегодня в этой статье я буду реализовывать Redux самостоятельно, чтобы глубже понять ее принципы. Мы по-прежнему придерживаемся старого подхода, начнем с базового использования, а затем реализуем Redux для замены исходного пакета NPM, но функция остается прежней. В этой статье будет реализована только основная библиотека Redux и она будет использоваться в сочетании с другими библиотеками, такими как React-Redux, и позже я напишу отдельную статью. Иногда мы слишком много внимания уделяем использованию, лишь вспоминая различные способы использования, но игнорируя их основные принципы, но если мы хотим действительно улучшить технологию, лучше всего разбираться в ней по порядку, например, Redux и React-Redux. кажутся очень похожими, но их основные концепции и проблемы разные. Redux на самом деле просто простая библиотека управления состоянием без каких-либо вещей, связанных с интерфейсом. React-Redux фокусируется на том, как объединить Redux с React, используя некоторые API React.
Весь код в этой статье был загружен на GitHub, вы можете снять его и поиграть:GitHub.com/Денис — см....
основная концепция
Концепция Redux была освещена во многих статьях, и все, должно быть, много ее читали, я не буду здесь ее расширять, просто кратко упомяну. Основные концепции Redux в основном включают следующее:
Store
Как следует из названия, Store — это склад. Он хранит все состояния и предоставляет некоторые API для работы с ним. Наши последующие операции фактически управляют этим складом. Если наш склад используется для хранения молока, изначально на нашем складе нет ни одного пакета молока, то состояние Магазина такое:
{
milk: 0
}
Actions
Действие - это действие. Цель этого действия - изменить определенное состояние в Магазине. Магазин - это еще склад выше. Сейчас я хочу поставить ящик молока на склад. Потом "Я хочу поставить ящик молока на складе" является Action. , код такой:
{
type: "PUT_MILK",
count: 1
}
Reducers
Я только подумал про "хочу поставить ящик молока на склад", но еще не сделал. Конкретная операция зависит от Редюсера. Редуктор меняет состояние в Магазине в соответствии с полученным Действием. например, я получилPUT_MILK
, количество одновременныхcount
равен 1, то результатом его ввода являетсяmilk
Увеличивается на 1, от 0 до 1, код выглядит так:
const initState = {
milk: 0
}
function reducer(state = initState, action) {
switch (action.type) {
case 'PUT_MILK':
return {...state, milk: state.milk + action.count}
default:
return state
}
}
Видно, что Redux сам по себе является простым конечным автоматом, Store хранит все состояния, а Action — уведомление об изменении состояния, Reducer меняет соответствующее состояние в Store при получении уведомления.
Простой пример
Давайте рассмотрим простой пример, который включает в себя вышеупомянутые концепции Store, Action и Reducer:
import { createStore } from 'redux';
const initState = {
milk: 0
};
function reducer(state = initState, action) {
switch (action.type) {
case 'PUT_MILK':
return {...state, milk: state.milk + action.count};
case 'TAKE_MILK':
return {...state, milk: state.milk - action.count};
default:
return state;
}
}
let store = createStore(reducer);
// subscribe其实就是订阅store的变化,一旦store发生了变化,传入的回调函数就会被调用
// 如果是结合页面更新,更新的操作就是在这里执行
store.subscribe(() => console.log(store.getState()));
// 将action发出去要用dispatch
store.dispatch({ type: 'PUT_MILK' }); // milk: 1
store.dispatch({ type: 'PUT_MILK' }); // milk: 2
store.dispatch({ type: 'TAKE_MILK' }); // milk: 1
реализовать это самостоятельно
Хотя наш предыдущий пример короткий, он уже содержит основные функции Redux, поэтому первая цель нашего почерка — заменить Redux в этом примере. Чтобы заменить этот Redux, мы должны сначала узнать, что в нем есть.Присмотревшись, кажется, что мы используем только один из его API:
createStore
: этот API принимаетreducer
метод в качестве параметра, возвращающийstore
, основные функции в этомstore
начальство.
посмотриstore
Что мы использовали выше:
store.subscribe
: подписыватьсяstate
меняется, когдаstate
Выполнять обратный вызов при его изменении, их может быть несколькоsubscribe
, обратные вызовы внутри будут выполняться последовательно.
store.dispatch
: проблемаaction
метод каждый разdispatch
action
будет выполнятьreducer
генерировать новыеstate
, затем выполнитеsubscribe
Зарегистрированный обратный вызов.
store.getState
: простой метод, возвращающий текущийstate
.
Видетьsubscribe
зарегистрировать обратный звонок,dispatch
Инициируйте обратный вызов, что приходит на ум, разве это не модель публикации-подписки?У меня есть предыдущая статья про модель публикации-подписки подробно, здесь прямая имитация.
function createStore() {
let state; // state记录所有状态
let listeners = []; // 保存所有注册的回调
function subscribe(callback) {
listeners.push(callback); // subscribe就是将回调保存下来
}
// dispatch就是将所有的回调拿出来依次执行就行
function dispatch() {
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener();
}
}
// getState直接返回state
function getState() {
return state;
}
// store包装一下前面的方法直接返回
const store = {
subscribe,
dispatch,
getState
}
return store;
}
Разве приведенный выше код не очень прост, ядро Redux также является моделью публикации-подписки, это так просто! Подождите, кажется, чего-то не хватает,reducer
Шерстяная ткань?reducer
Эффект должен измениться, когда событие будет опубликованоstate
, так что нашdispatch
должен быть выполнен перед выполнением обратного вызоваreducer
,использоватьreducer
Возвращаемое значениеstate
назначать,dispatch
Переписано следующим образом:
function dispatch(action) {
state = reducer(state, action);
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener();
}
}
На данный момент мы реализовали все API, использованные в предыдущих примерах, попробуем заменить официальный Redux нашим собственным Redux:
// import { createStore } from 'redux';
import { createStore } from './myRedux';
Вы можете видеть, что вывод такой же, что указывает на то, что с Redux, который мы написали, проблем нет:
После понимания основных принципов Redux у нас не должно возникнуть проблем с повторным просмотром его исходного кода.Исходный портал createStore.
Наконец, давайте разберемся с основным процессом Redux.Обратите внимание, что чистый Redux — это просто конечный автомат.View
О слой.
В дополнение к этой основной логике в Redux есть несколько очень интересных API, так что давайте запишем их.
почеркcombineReducers
combineReducers
Это также очень широко используемый API.Когда наше приложение становится все более и более сложным, если вся логика написана в одномreducer
Внутри, в конце концов, этот файл может иметь тысячи строк, поэтому Redux предоставляетcombineReducers
, что позволяет нам писать свои для разных модулейreducer
, и, наконец, объединить их. Например, молочный склад, с которого мы начинали, так как наш бизнес развивается очень хорошо, мы добавили еще один склад для амплификации риса, для этих двух складов мы можем создать свои собственные склады.reducer
, а затем объедините их следующим образом:
import { createStore, combineReducers } from 'redux';
const initMilkState = {
milk: 0
};
function milkReducer(state = initMilkState, action) {
switch (action.type) {
case 'PUT_MILK':
return {...state, milk: state.milk + action.count};
case 'TAKE_MILK':
return {...state, milk: state.milk - action.count};
default:
return state;
}
}
const initRiceState = {
rice: 0
};
function riceReducer(state = initRiceState, action) {
switch (action.type) {
case 'PUT_RICE':
return {...state, rice: state.rice + action.count};
case 'TAKE_RICE':
return {...state, rice: state.rice - action.count};
default:
return state;
}
}
// 使用combineReducers组合两个reducer
const reducer = combineReducers({milkState: milkReducer, riceState: riceReducer});
let store = createStore(reducer);
store.subscribe(() => console.log(store.getState()));
// 操作🥛的action
store.dispatch({ type: 'PUT_MILK', count: 1 }); // milk: 1
store.dispatch({ type: 'PUT_MILK', count: 1 }); // milk: 2
store.dispatch({ type: 'TAKE_MILK', count: 1 }); // milk: 1
// 操作大米的action
store.dispatch({ type: 'PUT_RICE', count: 1 }); // rice: 1
store.dispatch({ type: 'PUT_RICE', count: 1 }); // rice: 2
store.dispatch({ type: 'TAKE_RICE', count: 1 }); // rice: 1
В приведенном выше коде мы будемstate
разделен на два небольшихmilkState
а такжеriceState
, окончательный результат выглядит следующим образом:
Зная использование, давайте попробуем написать это сами! писать от рукиcombineReducers
, давайте сначала проанализируем, что он сделал, во-первых, его возвращаемое значение - этоreducer
,этоreducer
также будет действовать какcreateStore
Передаются параметры, указывающие, что это возвращаемое значение является общим.reducer
функция с той же структурой. Эта функция также получаетstate
а такжеaction
затем вернуть новыйstate
, только этот новыйstate
соблюдатьcombineReducers
Структура данных параметра. Попробуем написать:
function combineReducers(reducerMap) {
const reducerKeys = Object.keys(reducerMap); // 先把参数里面所有的键值拿出来
// 返回值是一个普通结构的reducer函数
const reducer = (state = {}, action) => {
const newState = {};
for(let i = 0; i < reducerKeys.length; i++) {
// reducerMap里面每个键的值都是一个reducer,我们把它拿出来运行下就可以得到对应键新的state值
// 然后将所有reducer返回的state按照参数里面的key组装好
// 最后再返回组装好的newState就行
const key = reducerKeys[i];
const currentReducer = reducerMap[key];
const prevState = state[key];
newState[key] = currentReducer(prevState, action);
}
return newState;
};
return reducer;
}
почеркapplyMiddleware
middleware
Это очень важная концепция в Redux.Экология Redux в основном зависит от этого доступа к API.Например, мы хотим написатьlogger
Промежуточное ПО можно написать так (это промежуточное ПО взято из официальной документации):
// logger是一个中间件,注意返回值嵌了好几层函数
// 我们后面来看看为什么这么设计
function logger(store) {
return function(next) {
return function(action) {
console.group(action.type);
console.info('dispatching', action);
let result = next(action);
console.log('next state', store.getState());
console.groupEnd();
return result
}
}
}
// 在createStore的时候将applyMiddleware作为第二个参数传进去
const store = createStore(
reducer,
applyMiddleware(logger)
)
Вы можете видеть, что приведенный выше код поддерживает промежуточное ПО,createStore
Поддерживается второй параметр, который официально называетсяenhancer
, как следует из названия, он является энхансером, используемым для усиленияstore
способность. официальный дляenhancer
определяется следующим образом:
type StoreEnhancer = (next: StoreCreator) => StoreCreator
Вышеупомянутая структура означает, чтоenhancer
В качестве функции он получаетStoreCreator
функция в качестве параметра, и возвращаемое значение также должно бытьStoreCreator
функция. Обратите внимание, что его возвращаемое значение также являетсяStoreCreator
Функция, то есть мы вынимаем ее возвращаемое значение и продолжаем выполнять, должна получиться такая же, как и предыдущая.createStore
Та же структура возврата, то есть наша предыдущаяcreateStore
Какая структура возвращается, он также должен вернуть структуру, которая является этойstore
:
{
subscribe,
dispatch,
getState
}
createStore
служба поддержкиenhancer
По его словамenhancer
определение, давайте перепишем своеcreateStore
, пусть поддерживаетenhancer
:
function createStore(reducer, enhancer) { // 接收第二个参数enhancer
// 先处理enhancer
// 如果enhancer存在并且是函数
// 我们将createStore作为参数传给他
// 他应该返回一个新的createStore给我
// 我再拿这个新的createStore执行,应该得到一个store
// 直接返回这个store就行
if(enhancer && typeof enhancer === 'function'){
const newCreateStore = enhancer(createStore);
const newStore = newCreateStore(reducer);
return newStore;
}
// 如果没有enhancer或者enhancer不是函数,直接执行之前的逻辑
// 下面这些代码都是之前那版
// 省略n行代码
// .......
const store = {
subscribe,
dispatch,
getState
}
return store;
}
Исходный код, соответствующий этой части, находится здесь.
applyMiddleware
Возвращаемое значение представляет собойenhancer
Мы уже имеемenhancer
основная структураapplyMiddleware
передается вторым параметром вcreateStore
Да, то есть онenhancer
, точнееapplyMiddleware
Возвращаемое значение представляет собойenhancer
, потому что мы переходим кcreateStore
это результат его казниapplyMiddleware()
:
function applyMiddleware(middleware) {
// applyMiddleware的返回值应该是一个enhancer
// 按照我们前面说的enhancer的参数是createStore
function enhancer(createStore) {
// enhancer应该返回一个新的createStore
function newCreateStore(reducer) {
// 我们先写个空的newCreateStore,直接返回createStore的结果
const store = createStore(reducer);
return store
}
return newCreateStore;
}
return enhancer;
}
выполнитьapplyMiddleware
Выше у нас уже естьapplyMiddleware
Базовая структура промежуточного программного обеспечения завершена, но функция еще не реализована.Чтобы реализовать его функцию, мы должны сначала выяснить, какую функцию имеет промежуточное программное обеспечение, или использовать предыдущуюlogger
Промежуточное ПО в качестве примера:
function logger(store) {
return function(next) {
return function(action) {
console.group(action.type);
console.info('dispatching', action);
let result = next(action);
console.log('next state', store.getState());
console.groupEnd();
return result
}
}
}
Это промежуточное ПО работает следующим образом:
может видеть насlet result = next(action);
После выполнения этой строкиstate
Изменилось, мы сказали раньше, что нам нужно изменитьсяstate
Толькоdispatch(action)
, так вотnext(action)
то естьdispatch(action)
, просто сменили имя. И обратите внимание на возвращаемое значение последнего слояreturn function(action)
, параметры которогоaction
, разве не похожеdispatch(action)
на самом деле он новыйdispatch(action)
, этот новыйdispatch(action)
буду называть оригиналdispatch
и добавить собственную логику до и после вызова. Итак, здесь структура промежуточного программного обеспечения также ясна:
- Промежуточное ПО получает
store
В качестве параметра он вернет функцию- Возвращаемая функция получает старый
dispatch
функция в качестве параметра, вернет новую функцию- Новая функция, которая возвращает, является новой
dispatch
Функция, эта функция может получить входящие два слоя извне.store
и старыйdispatch
функция
Грубо говоря, промежуточное ПО должно укреплятьdispatch
функция, с новымdispatch
заменить старыйdispatch
,这不就是个装饰者模式吗? На самом деле, преждеenhancer
Это также шаблон декоратора, переходящий вcreateStore
,существуетcreateStore
Добавьте немного кода до и после выполнения и, наконец, верните расширенную версию.createStore
.Видно, что паттерны проектирования действительно широко распространены в этих превосходных фреймворках.Если вы не знакомы с паттерном декоратора, то можете прочитать мою предыдущую статью.
Следуя этому ходу мысли, нашаapplyMiddleware
Вы можете написать это:
// 直接把前面的结构拿过来
function applyMiddleware(middleware) {
function enhancer(createStore) {
function newCreateStore(reducer) {
const store = createStore(reducer);
// 将middleware拿过来执行下,传入store
// 得到第一层函数
const func = middleware(store);
// 解构出原始的dispatch
const { dispatch } = store;
// 将原始的dispatch函数传给func执行
// 得到增强版的dispatch
const newDispatch = func(dispatch);
// 返回的时候用增强版的newDispatch替换原始的dispatch
return {...store, dispatch: newDispatch}
}
return newCreateStore;
}
return enhancer;
}
Как обычно с нашимиapplyMiddleware
Замена старого имеет тот же эффект при запуске, а значит, то, что мы написали, не проблема, ха-ха~
поддерживает несколькоmiddleware
нашapplyMiddleware
Есть еще одна функция, которая заключается в поддержке несколькихmiddleware
, например так:
applyMiddleware(
rafScheduler,
timeoutScheduler,
thunk,
vanillaPromise,
readyStatePromise,
logger,
crashReporter
)
На самом деле это легко поддержать, мы возвращаемсяnewDispatch
который будет передан в свою очередьmiddleware
Просто выньте его и выполните.Последовательное выполнение нескольких функций может использовать вспомогательные функцииcompose
, эта функция определяется следующим образом. Просто знайте, что у нас здесьcompose
Вы не можете просто использовать метод для его выполнения, вы должны вернуть метод, который обертывает все методы.
function compose(...func){
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
этоcompose
Это может сбивать с толку, я объясню это здесь, например, у нас есть три функции, и все эти три функции получены перед нами.dispatch
Новое возвращениеdispatch
Методы:
const fun1 = dispatch => newDispatch1;
const fun2 = dispatch => newDispatch2;
const fun3 = dispatch => newDispatch3;
когда мы использовалиcompose(fun1, fun2, fun3)
Какая последовательность выполнения после?
// 第一次其实执行的是
(func1, func2) => (...args) => func1(fun2(...args))
// 这次执行完的返回值是下面这个,用个变量存起来吧
const temp = (...args) => func1(fun2(...args))
// 我们下次再循环的时候其实执行的是
(temp, func3) => (...args) => temp(func3(...args));
// 这个返回值是下面这个,也就是最终的返回值,其实就是从func3开始从右往左执行完了所有函数
// 前面的返回值会作为后面参数
(...args) => temp(func3(...args));
// 再看看上面这个方法,如果把dispatch作为参数传进去会是什么效果
(dispatch) => temp(func3(dispatch));
// 然后func3(dispatch)返回的是newDispatch3,这个又传给了temp(newDispatch3),也就是下面这个会执行
(newDispatch3) => func1(fun2(newDispatch3))
// 上面这个里面用newDispatch3执行fun2(newDispatch3)会得到newDispatch2
// 然后func1(newDispatch2)会得到newDispatch1
// 注意这时候的newDispatch1其实已经包含了newDispatch3和newDispatch2的逻辑了,将它拿出来执行这三个方法就都执行了
Более подробно о принципе компоновки можно прочитать в моей предыдущей статье.
Поэтому мы поддерживаем несколькоmiddleware
Код такой:
// 参数支持多个中间件
function applyMiddleware(...middlewares) {
function enhancer(createStore) {
function newCreateStore(reducer) {
const store = createStore(reducer);
// 多个middleware,先解构出dispatch => newDispatch的结构
const chain = middlewares.map(middleware => middleware(store));
const { dispatch } = store;
// 用compose得到一个组合了所有newDispatch的函数
const newDispatchGen = compose(...chain);
// 执行这个函数得到newDispatch
const newDispatch = newDispatchGen(dispatch);
return {...store, dispatch: newDispatch}
}
return newCreateStore;
}
return enhancer;
}
Наконец, мы добавляем еще одинlogger2
Эффект реализации промежуточного ПО:
function logger2(store) {
return function(next) {
return function(action) {
let result = next(action);
console.log('logger2');
return result
}
}
}
let store = createStore(reducer, applyMiddleware(logger, logger2));
можно увидетьlogger2
Он также был распечатан, и все готово.
Теперь мы также можем понять, почему его промежуточное ПО включает в себя несколько слоев функций:
Уровень 1: Цель состоит в том, чтобы передать
store
параметрВторой слой: Структура второго слоя
dispatch => newDispatch
, этот уровень функций нескольких промежуточных программ можетcompose
подняться и образовать большуюdispatch => newDispatch
Третий уровень: этот уровень является окончательным возвращаемым значением, которое на самом деле
newDispatch
, улучшенdispatch
, это реальная логика промежуточного программного обеспечения.
сюда нашapplyMiddleware
только что закончил писать,Соответствующий исходный код можно увидеть здесь, я считаю, что после прочтения этой статьи и последующего просмотра исходного кода проблем не будет!
Весь код в этой статье был загружен на GitHub, вы можете пойти и поиграть с ним:GitHub.com/Денис — см....
Суммировать
- Pure Redux — это просто конечный автомат,
store
В нем хранятся все состоянияstate
, чтобы изменить состояние внутриstate
, Толькоdispatch action
. - для выпущенных
action
нужно использоватьreducer
иметь дело с,reducer
будет вычислять новыеstate
заменить старыйstate
. -
subscribe
метод может регистрировать методы обратного вызова, когдаdispatch action
выполнит обратный вызов внутри. - Redux на самом деле является моделью публикации-подписки!
- Redux также поддерживает
enhancer
,enhancer
По сути, это режим декоратора, переходящий в текущийcreateStore
, который возвращает расширенныйcreateStore
. - Использование Redux
applyMiddleware
поддержка промежуточного программного обеспечения,applyMiddleware
Возвращаемое значение на самом делеenhancer
. - Промежуточное ПО Redux также является шаблоном декоратора, переходящим в текущий
dispatch
, который возвращает расширенныйdispatch
. - Pure Redux не имеет слоя представления, поэтому его можно использовать в сочетании с различными библиотеками пользовательского интерфейса, такими как
react-redux
, план следующей статьи рукописныйreact-redux
.
использованная литература
Официальная документация:redux.js.org/
Исходный код GitHub:GitHub.com/Горячее цинкование/Горячее…
В конце статьи спасибо, что потратили свое драгоценное время на чтение этой статьи. Если эта статья немного поможет вам или вдохновит, пожалуйста, не скупитесь на лайки и звезды GitHub. Ваша поддержка является движущей силой для автор продолжать творить.
Добро пожаловать, чтобы обратить внимание на мой общедоступный номербольшой фронт атакиПолучите высококачественные оригиналы впервые~
Цикл статей "Передовые передовые знания":nuggets.capable/post/684490…
Адрес GitHub с исходным кодом из серии статей «Advanced Front-end Knowledge»:GitHub.com/Денис — см....