[Серия React] Реализация Redux с нуля

JavaScript Redux
[Серия React] Реализация Redux с нуля

Эта статья будет написана с нуляRedux, если ты правReduxИспользование и исходный код .ReduxБар. Знай это, знай почему.

Код, соответствующий этой статье, находится в:Github.com/yvette la U/b ..., рекомендуется сначалаcloneкод, прочитайте эту статью против кода.

1. Reduxчто это такое?

ReduxдаJavaScriptКонтейнер состояния, обеспечивающий предсказуемое управление состоянием.Reduxкроме иReactПомимо совместного использования, также поддерживаются другие интерфейсные библиотеки.ReduxМаленький и мощный, только2KB. Здесь нам нужно внести ясность:Reduxа такжеReactМежду ними нет прочной связующей связи. Эта статья направлена ​​на то, чтобы понять и реализоватьRedux, но не включаетreact-redux(Вы можете глубоко понять одну точку знания за раз,react-reduxпоявится в следующей статье).

2. С нуля для достиженияRedux

Давайте забудемReduxпонятие, начиная с примера, используяcreate-react-appСоздайте проект:to-redux.

Кодовый адрес:myredux/to-reduxсередина.

Будуpublic/index.htmlсерединаbodyИзменено следующим образом:

<div id="app">
    <div id="header">
        前端宇宙
    </div>
    <div id="main">
        <div id="content">大家好,我是前端宇宙作者刘小夕</div>
        <button class="change-theme" id="to-blue">Blue</button>
        <button class="change-theme" id="to-pink">Pink</button>
    </div>
</div>

Функция, которую мы хотим реализовать, показана на рисунке выше.При нажатии кнопки можно изменить цвет шрифта всего приложения.

Исправлятьsrc/index.jsследующим образом (код:to-redux/src/index1.js):

let state = {
    color: 'blue'
}
//渲染应用
function renderApp() {
    renderHeader();
    renderContent();
}
//渲染 title 部分
function renderHeader() {
    const header = document.getElementById('header');
    header.style.color = state.color;
}
//渲染内容部分
function renderContent() {
    const content = document.getElementById('content');
    content.style.color = state.color;
}

renderApp();

//点击按钮,更改字体颜色
document.getElementById('to-blue').onclick = function () {
    state.color = 'rgb(0, 51, 254)';
    renderApp();
}
document.getElementById('to-pink').onclick = function () {
    state.color = 'rgb(247, 109, 132)'; 
    renderApp();
}

Это приложение очень простое, но у него есть одна проблема:stateявляется общим состоянием, но любой может изменить его, как только мы изменим это состояние по своему желанию, это может вызвать ошибки, например, вrenderHeaderвнутри, наборstate = {}, легко вызвать непредвиденные ошибки.

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

//在 index.js 中继续追加代码
function changeState(action) {
    switch(action.type) {
        case 'CHANGE_COLOR':
            return {
                ...state,
                color: action.color
            }
        default:
            return state;
    }
}

Мы договорились, что толькоchangeStateчтобы изменить состояние, он принимает параметрaction,ВключатьtypeПоле обычных объектов,typeПоля используются для определения типа вашего действия (т.е. как изменить состояние).

Мы хотим нажать кнопку, чтобы изменить цвет шрифта всего приложения.

//在 index.js 中继续追加代码
document.getElementById('to-blue').onclick = function() {
    let state = changeState({
        type: 'CHANGE_COLOR',
        color: 'rgb(0, 51, 254)'
    });
    //状态修改完之后,需要重新渲染页面
    renderApp(state);
}

document.getElementById('to-pink').onclick = function() {
    let state = changeState({
        type: 'CHANGE_COLOR',
        color: 'rgb(247, 109, 132)'
    });
    renderApp(state);
}

оторваться от магазина

Хотя теперь мы договариваемся о том, как модифицировать состояние, ноstateявляется глобальной переменной, мы можем легко изменить ее, поэтому мы можем рассмотреть возможность сделать ее локальной переменной, определив ее внутри функции (createStore), но внешне тоже нужно использоватьstateпоэтому нам нужно предоставить методgetState(), так что мыcreateStoreполученный извнеstate.

function createStore (state) {
    const getState = () => state;
    return {
        getState
    }
}

Теперь мы можем пройтиstore.getState()способ получения статуса (здесь необходимо пояснить, чтоstateОбычно объект, поэтому этот объект может быть непосредственно модифицирован снаружи, но если глубокая копияstateВозвращение, то он не должен быть изменен извне, так какreduxИсходный код возвращается напрямуюstate, здесь мы не выполняем глубокое копирование (в конце концов, это стоит производительности).

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

function createStore (state) {
    const getState = () => state;
    const changeState = () => {
        //...changeState 中的 code
    }
    return {
        getState,
        changeState
    }
}

Сейчас,index.jsСредний код становится следующим (to-redux/src/index2.js):

function createStore() {
    let state = {
        color: 'blue'
    }
    const getState = () => state;
    function changeState(action) {
        switch (action.type) {
            case 'CHANGE_COLOR':
                state = {
                    ...state,
                    color: action.color
                }
                return state;
            default:
                return state;
        }
    }
    return {
        getState,
        changeState
    }
}

function renderApp(state) {
    renderHeader(state);
    renderContent(state);
}
function renderHeader(state) {
    const header = document.getElementById('header');
    header.style.color = state.color;
}
function renderContent(state) {
    const content = document.getElementById('content');
    content.style.color = state.color;
}

document.getElementById('to-blue').onclick = function () {
    store.changeState({
        type: 'CHANGE_COLOR',
        color: 'rgb(0, 51, 254)'
    });
    renderApp(store.getState());
}
document.getElementById('to-pink').onclick = function () {
    store.changeState({
        type: 'CHANGE_COLOR',
        color: 'rgb(247, 109, 132)'
    });
    renderApp(store.getState());
}
const store = createStore();
renderApp(store.getState());

Хотя мы сейчас оторвалисьcreateStoreметод, но очевидно, что этот метод вовсе не универсален,stateа такжеchangeStateметоды определены вcreateStoreсередина. В этом случае другие приложения не могут повторно использовать этот режим.

changeStateЛогика должна быть определена снаружи, поскольку логика для модификации состояния обязательна для каждого приложения. Мы разбиваем эту часть логики снаружи и переименуйте ееreducer(Спросите, почему это называетсяreducer, вопрос за иreduxбыть последовательным).reducerДля чего она, грубо говоря, основана наactionтипа вычисляется новое состояние. потому что это неcreateStoreОпределено внутри, недоступно напрямуюstate, поэтому нам нужно представить его как параметр, передаваемый в состояние. следующим образом:

function reducer(state, action) {
    switch(action.type) {
        case 'CHANGE_COLOR':
            return {
                ...state,
                color: action.color
            }
        default:
            return state;
    }
}

createStore Evolved

function createStore(reducer) {
    let state = {
        color: 'blue'
    }
    const getState = () => state;
    //将此处的 changeState 更名为 `dispatch`
    const dispatch = (action) => {
        //reducer 接收老状态和action,返回一个新状态
        state = reducer(state, action);
    }
    return {
        getState,
        dispatch
    }
}

для разных приложенийstateдолжно быть иначе, мы будемstateЗначение определяется вcreateStoreВнутренне он должен быть иррациональным.

function createStore(reducer) {
    let state;
    const getState = () => state;
    const dispatch = (action) => {
        //reducer(state, action) 返回一个新状态
        state = reducer(state, action);
    }
    return {
        getState,
        dispatch
    }
}

Вниманиеreducer

КstateЕсть начальное состояние, которое на самом деле очень простое.stateЗначение инициализации равноreducerзначение параметра по умолчанию, затем вcreateStoreраспространять одинreducer看不懂的动作就可以了。 такgetStateПри первом вызове можно получить значение состояния по умолчанию.

createStore Evolved 2.0

function createStore(reducer) {
    let state;
    const getState = () => state;
    //每当 `dispatch` 一个动作的时候,我们需要调用 `reducer` 以返回一个新状态
    const dispatch = (action) => {
        //reducer(state, action) 返回一个新状态
        state = reducer(state, action);
    }
    //你要是有个 action 的 type 的值是 `@@redux/__INIT__${Math.random()}`,我敬你是个狠人
    dispatch({ type: `@@redux/__INIT__${Math.random()}` });

    return {
        getState,
        dispatch
    }
}

теперь этоcreateStoreЕго можно использовать везде, но чувствуете ли вы, что каждый разdispatchПосле этого оба вручнуюrenderApp()Кажется глупым, текущее приложение вызывается два раза, если нужно модифицировать 1000 разstateЭто вружно звонить в 1000 раз?renderApp()?

Можешь упростить? Вызывается автоматически каждый раз при изменении данныхrenderApp(). Конечно, мы не можемrenderApp()написать наcreateStore()изdispatch, потому что в других приложениях имя функции может не вызыватьсяrenderApp(), и можно вызвать болееrenderApp(). можно импортировать здесь发布订阅模式, уведомлять всех подписчиков об изменении состояния.

createStore Evolved 3.0

function createStore(reducer) {
    let state;
    let listeners = [];
    const getState = () => state;
    //subscribe 每次调用,都会返回一个取消订阅的方法
    const subscribe = (ln) => { 
        listeners.push(ln);
        //订阅之后,也要允许取消订阅。
        //难道我订了某本杂志之后,就不允许我退订吗?可怕~
        const unsubscribe = () => {
            listeners = listeners.filter(listener => ln !== listener);
        }
        return unsubscribe;
    };
    const dispatch = (action) => {
        //reducer(state, action) 返回一个新状态
        state = reducer(state, action);
        listeners.forEach(ln => ln());
        
    }
    //你要是有个 action 的 type 的值正好和 `@@redux/__INIT__${Math.random()}` 相等,我敬你是个狠人
    dispatch({ type: `@@redux/__INIT__${Math.random()}` });

    return {
        getState,
        dispatch,
        subscribe
    }
}

Пока самый простойreduxбыл создан,createStoreдаreduxОсновной. Давайте воспользуемся этой сокращенной версиейreduxпереписать наш код,index.jsСодержимое файла обновляется следующим образом (to-redux/src/index.js):

function createStore() {
    //code(自行将上面createStore的代码拷贝至此处)
}
const initialState = {
    color: 'blue'
}

function reducer(state = initialState, action) {
    switch (action.type) {
        case 'CHANGE_COLOR':
            return {
                ...state,
                color: action.color
            }
        default:
            return state;
    }
}
const store = createStore(reducer);

function renderApp(state) {
    renderHeader(state);
    renderContent(state);
}
function renderHeader(state) {
    const header = document.getElementById('header');
    header.style.color = state.color;
}
function renderContent(state) {
    const content = document.getElementById('content');
    content.style.color = state.color;
}

document.getElementById('to-blue').onclick = function () {
    store.dispatch({
        type: 'CHANGE_COLOR',
        color: 'rgb(0, 51, 254)'
    });
}
document.getElementById('to-pink').onclick = function () {
    store.dispatch({
        type: 'CHANGE_COLOR',
        color: 'rgb(247, 109, 132)'
    });
}

renderApp(store.getState());
//每次state发生改变时,都重新渲染
store.subscribe(() => renderApp(store.getState()));

Если теперь мы хотим нажатьPinkПосле этого цвет шрифта менять нельзя, тогда тоже можно отписаться:

const unsub = store.subscribe(() => renderApp(store.getState()));
document.getElementById('to-pink').onclick = function () {
    //code...
    unsub(); //取消订阅
}

Кстати:reducerЭто чистая функция (если вы не понимаете концепцию чистой функции, проверьте информацию сами), она получает предыдущийstateа такжеaction, И возвращает новыйstate. не спрашивай почемуactionдолжен иметь вtypeполе, это просто условность (reduxТак было задумано)

Наследие: почемуreducerОбязательно верните новыйstate, вместо прямого измененияstateШерстяная ткань. Не стесняйтесь оставлять свой ответ в области комментариев.

Мы выводили шаг за шагомreduxОсновной код , теперь давайте рассмотримreduxДизайнерское мышление:

Reduxдизайн-мышление

  • Reduxвсе состояние приложения (state) к месту (обычно мы называем егоstore)
  • Когда нам нужно изменить состояние, мы должны отправить (dispatch)Одинaction( actionэто сtypeполевой объект)
  • Специализированные обработчики состоянийreducerполучить старыйstateа такжеaction, и вернет новыйstate
  • пройти черезsubscribeНастройте подписку, чтобы уведомлять всех подписчиков каждый раз, когда отправляется действие.

Теперь у нас есть базовая версияredux, но это все еще не соответствует нашим потребностям. Наше обычное развитие бизнеса не будет таким простым, как в примере, написанном выше, тогда возникнет проблема:reducerфункции могут быть очень длинными, потому чтоactionТип будет очень. Это, безусловно, не способствует написанию и чтению кода.

Только представьте, что есть сто видов вашего бизнесаactionНужно разобраться, записать эти сотни ситуаций вreducerВ , мало того, что писать противно, так ещё и коллеги, которые поддерживают код в более поздний период, хотят убивать людей.

Поэтому лучше напишем отдельноreducer, тогда правильноreducerОбъединить. есть у насcombineReducers(а такжеreduxНазвание библиотеки осталось прежним) дебют~

combineReducers

Сначала нам нужно внести ясность:combineReducersПросто функция полезности, как мы уже говорили ранее, она будет многократноreducerсливаться в одинreducer.combineReducersто, что возвращаетсяreducer, то есть это функция высшего порядка.

Мы все же объясним на примере, хотяreduxне надо миритьсяreactсотрудничества, но с учетом егоreactСотрудничество является наиболее подходящим, здесь, сreactПример кода:

На этот раз мы добавили функцию счетчика (используяReactРефакторинг ===>to-redux2):

//现在我们的 state 结构如下:
let state = {
    theme: {
        color: 'blue'
    },
    counter: {
        number: 0
    }
}

Очевидно, модификацию субъекта и счетчика можно разделить по разнымreducerСобираться на сделку - лучший вариант.

store/reducers/counter.js

Отвечает за обработку состояния счетчика.

import { INCRENENT, DECREMENT } from '../action-types';

export default counter(state = {number: 0}, action) {
    switch (action.type) {
        case INCRENENT:
            return {
                ...state,
                number: state.number + action.number
            }
        case DECREMENT:
            return {
                ...state,
                number: state.number - action.number
            }
        default:
            return state;
    }
}

store/reducers/theme.js

Отвечает за обработку состояния, которое изменяет цвет темы.

import { CHANGE_COLOR } from '../action-types';

export default function theme(state = {color: 'blue'}, action) {
    switch (action.type) {
        case CHANGE_COLOR:
            return {
                ...state,
                color: action.color
            }
        default:
            return state;
    }
}

каждыйreducerОтвечает только за управление глобальнымstateчасть отвечает. каждыйreducerизstateПараметры разные, соответствующие той части, которой он управляетstateданные.

import counter from './counter';
import theme from './theme';

export default function appReducer(state={}, action) {
    return {
        theme: theme(state.theme, action),
        counter: counter(state.counter, action)
    }
}

appReducerпосле слиянияreducer, но когдаreducerКогда их много, писать так громоздко, поэтому мы пишем инструментальную функцию для генерации такогоappReducerМы называем эту утилитую функцию какcombineReducers.

Попробуем написать эту служебную функциюcombineReducers:

Идеи:

  1. combineReducersвернутьreducer
  2. combineReducersВходных параметров несколькоreducerсоставной объект
  3. каждыйreducerобрабатывать только глобальныеstateта часть, за которую вы отвечаете
//reducers 是一个对象,属性值是每一个拆分的 reducer
export default function combineReducers(reducers) {
    return function combination(state={}, action) {
        //reducer 的返回值是新的 state
        let newState = {};
        for(var key in reducers) {
            newState[key] = reducers[key](state[key], action);
        }
        return newState;
    }
}

сынreducerstateЗначения по умолчанию. В этом примерном варианте осуществленияcreateStoreсерединаdispatch({type:@@redux/INIT${Math.random()}}), при переходе наcreateStoreдаcombineReducers(reducers)функция, которая возвращаетcombination.

согласно сstate=reducer(state,action),newState.theme=theme(undefined, action), newState.counter=counter(undefined, action),counterа такжеthemeдва сынаreducerвернуть отдельноnewState.themeа такжеnewState.counterначальное значение .

воспользоваться этимcombineReducersможно переписатьstore/reducers/index.js

import counter from './counter';
import theme from './theme';
import { combineReducers } from '../redux';
//明显简洁了许多~
export default combineReducers({
    counter,
    theme
});

мы написалиcombineReducersХотя кажется, что он удовлетворяет наши потребности, у него есть недостаток, то есть он вернет новый.stateобъект, что приводит к бессмысленному повторному рендерингу, когда данные не изменились. Следовательно, мы можем судить о данных и вернуться к исходному состоянию, когда данные не изменились.stateВот и все.

Усовершенствованная версия CombineReducers

//代码中省略了一些判断,默认传递的参数均是符合要求的,有兴趣可以查看源码中对参数合法性的判断及处理
export default function combineReducers(reducers) {
    return function combination(state={}, action) {
        let nextState = {};
        let hasChanged = false; //状态是否改变
        for(let key in reducers) {
            const previousStateForKey = state[key];
            const nextStateForKey = reducers[key](previousStateForKey, action);
            nextState[key] = nextStateForKey;
            //只有所有的 nextStateForKey 均与 previousStateForKey 相等时,hasChanged 的值才是 false
            hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
        }
        //state 没有改变时,返回原对象
        return hasChanged ? nextState : state;
    }
}

applyMiddleware

официальная документацияпримерноapplyMiddlewareОбъяснение очень понятное, и следующее также относится к содержанию официальной документации:

Ведение журнала

Рассмотрим небольшую проблему, если мы хотим иметь возможность печатать в консоли каждый раз, когда изменяется состояниеstate, Так что же нам делать?

Самый простой:

//...
<button onClick={() => {
    console.log(store.getState());
    store.dispatch(actions.add(2));
}}>+</button>
//...

Конечно, этот метод определенно не рекомендуется, если мы 100 раз диспетчеризируем в нашем коде, мы не сможем написать это 100 раз. Так как он печатается при изменении состоянияstate, то есть вreducerраспечатать перед звонкомstate, reducerвdispatchвызван, то мы можем переопределитьstore.dispatchспособ, распечатать перед отправкойstateВот и все.

let store = createStore(reducer);
const next = store.dispatch; //next 的命令是为了和中间件的源码一致
store.dispatch = action => {
    console.log(store.getState());
    next(action);
}

информация о сбое

Предположим, нам нужно больше, чем просто печатьstate, вам также необходимо распечатать сообщение об ошибке при отправке исключения.

const next = store.dispatch; //next 的命名是为了和中间件的源码一致
store.dispatch = action => {
    try{
        console.log(store.getState());
        next(action);
    } catct(err) {
        console.error(err);
    }
}

И если у нас есть другие потребности, то нам нужно остановить измененияstore.dispatchметод, который, наконец, делает эту часть кода трудной для сопровождения.

Итак, нам нужно отделитьloggerMiddlewareа такжеexceptionMiddleware.

let store = createStore(reducer);
const next = store.dispatch; //next 的命名是为了和中间件的源码一致
const loggerMiddleware = action => {
    console.log(store.getState());
    next(action);
}
const exceptionMiddleware = action => {
    try{
        loggerMiddleware(action);
    }catch(err) {
        console.error(err);
    }
}
store.dispatch = exceptionMiddleware;

Мы знаем, что многиеmiddlewareпредоставляются третьими лицами, поэтому необходимоdispatchа такжеgetStateОн должен быть передан как параметр дляmiddleware, далее переписано:

const loggerMiddleware = ({dispatch, getState}) => action => {
    const next = dispatch;
    console.log(getState());
    next(action);
}
const exceptionMiddleware = ({dispatch, getState}) => action => {
    try{
        loggerMiddleware({dispatch, getState})(action);
    }catch(err) {
        console.error(err);
    }
}

//使用
store.dispatch = exceptionMiddleware({dispatch, getState});

Теперь есть небольшая проблема,exceptionMiddlewareсерединаloggerMiddlewareЭто жестко закодировано, что определенно неразумно. Мы надеемся, что это параметр, чтобы его можно было использовать гибко. Нет причин толькоexceptionMiddlewareнужно быть гибким, независимо отloggerMiddlewareДалее читаем следующее:

const loggerMiddleware = ({dispatch, getState}) => next => action => {
    console.log(getState());
    return next(action);
}
const exceptionMiddleware = ({dispatch, getState}) => next => action => {
    try{
        return next(action);
    }catch(err) {
        console.error(err);
    }
}
//使用
const next = store.dispatch;
const logger = loggerMiddleware({dispatch: store.dispatch, getState: store.getState});
store.dispatch = exceptionMiddleware(store)(logger(next));

middlewareПисьменный формат.

middlewareполучилnext()изdispatchфункция и возвращаетdispatchфункция, возвращенная функция будет использоваться как следующаяmiddlewareизnext()

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

Как мы видим выше, в итоге мы хотим изменитьdispatch, поэтому нам нужно переписатьstore, вернуть измененныйdispatchПосле методаstore.

Итак, можем уточнить следующие моменты:

  1. applyMiddlewareВозвращаемое значениеstore
  2. applyMiddlewareопределенно принятьmiddlewareкак параметр
  3. applyMiddlewareпринять{dispatch, getState}как ввод, ноreduxВходной параметр в исходном кодеcreateStoreа такжеcreateStoreВходные параметры, вдумайтесь, нет необходимости создавать внешнийstore, ведь создано извнеstoreКроме того, что он передается в функцию в качестве параметра, он не имеет никакого другого эффекта, лучше поставитьcreateStoreа такжеcreateStoreПередаются параметры, которые необходимо использовать.
//applyMiddleWare 返回 store.
const applyMiddleware = middleware => createStore => (...args) => {
    let store = createStore(...args);
    let middle = loggerMiddleware(store);
    let dispatch = middle(store.dispatch); //新的dispatch方法
    //返回一个新的store---重写了dispatch方法
    return {
        ...store,
        dispatch
    }
}

ВышеупомянутоеmiddlewareСитуация, но мы знаем,middlewareИли вполне вероятно, и больше мы в основном решаемmiddlewareвопрос, далее перефразированный.

//applyMiddleware 返回 store.
const applyMiddleware = (...middlewares) => createStore => (...args) => {
    let store = createStore(...args);
    let dispatch;
    const middlewareAPI = {
        getState: store.getstate,
        dispatch: (...args) => dispatch(...args)
    }
    //传递修改后的 dispatch
    let middles = middlewares.map(middleware => middleware(middlewareAPI));
    //现在我们有多个 middleware,需要多次增强 dispatch
    dispatch = middles.reduceRight((prev, current) => current(prev), store.dispatch);
    return {
        ...store,
        dispatch
    }
}

Я не знаю, понимаете ли вы вышесказанноеmiddles.reduceRight, ниже приводится подробное описание для вас:

/*三个中间件*/
let logger1 = ({dispatch, getState}) => dispatch => action => {
    console.log('111');
    dispatch(action);
    console.log('444');
}
let logger2 = ({ dispatch, getState }) => dispatch => action => {
    console.log('222');
    dispatch(action);
    console.log('555')
}
let logger3 = ({ dispatch, getState }) => dispatch => action => {
    console.log('333');
    dispatch(action);
    console.log('666');
}
let middle1 = logger1({ dispatch, getState });
let middle2 = logger2({ dispatch, getState });
let middle3 = logger3({ dispatch, getState });

//applyMiddleware(logger1,logger2,logger3)(createStore)(reducer)
//如果直接替换
store.dispatch = middle1(middle2(middle3(store.dispatch)));

соблюдать вышеизложенноеmiddle1(middle2(middle3(store.dispatch))), если положитьmiddle1,middle2,middle3Он рассматривается как каждый элемент массива.Если вы знакомы с API массива, вы можете подумать об этом.reduce, если вы еще не знакомы сreduce, вы можете просмотретьДокументация MDN.

//applyMiddleware(logger1,logger3,logger3)(createStore)(reducer)

//reduceRight 从右到左执行
middles.reduceRight((prev, current) => current(prev), store.dispatch);
//第一次 prev: store.dispatch    current: middle3  
//第二次 prev: middle3(store.dispatch) current: middle2
//第三次 prev: middle2(middle3(store.dispatch))  current: middle1
//结果 middle1(middle2(middle3(store.dispatch)))

читатьreduxИсточник студентов, может знать исходный код снабженcomposeФункция иcomposeне используется в функцииreduceRight, Вместо того, чтобы использоватьreduce, поэтому код немного отличается. Но процесс анализа остается прежним.

compose.js

export default function compose(...funcs) {
    //如果没有中间件
    if (funcs.length === 0) {
        return arg => arg
    }
    //中间件长度为1
    if (funcs.length === 1) {
        return funcs[0]
    }

    return funcs.reduce((prev, current) => (...args) => prev(current(...args)));
}

оreduceСпособ написания, рекомендуется, как указано вышеreduceRightто же самое, сделать анализ

использоватьcomposeПерезапись служебной функцииapplyMiddleware.

const applyMiddleware = (...middlewares) => createStore => (...args) => {
    let store = createStore(...args);
    let dispatch;
    const middlewareAPI = {
        getState: store.getstate,
        dispatch: (...args) => dispatch(...args)
    }
    let middles = middlewares.map(middleware => middleware(middlewareAPI));
    dispatch = compose(...middles)(store.dispatch);
    return {
        ...store,
        dispatch
    }
}

bindActionCreators

reduxтакже предоставляет намbindActionCreatorsИнструментальная функция, код этой инструментальной функции очень прост, мы редко используем ее непосредственно в коде,react-reduxбудет использоваться в . Вот кратко объясните:

//通常我们会这样编写我们的 actionCreator
import { INCRENENT, DECREMENT } from '../action-types';

const counter = {
    add(number) {
        return {
            type: INCRENENT,
            number
        }
    },
    minus(number) {
        return {
            type: DECREMENT,
            number
        }
    }
}

export default counter;

При отправке нам нужно написать:

import counter from 'xx/xx';
import store from 'xx/xx';

store.dispatch(counter.add());

Конечно, мы могли бы также написать наш actionCreator следующим образом:

function add(number) {
    return {
        type: INCRENENT,
        number
    }
}

При отправке нужно написать так:

store.dispatch(add(number));

Приведенные выше коды имеют одну общую черту, то есть все ониstore.dispatchОтправляет действие. Таким образом, мы можем рассмотреть возможность написания функции, которая будетstore.dispatchа такжеactionCreatorСвязывать.

function bindActionCreator(actionCreator, dispatch) {
    return  (...args) => dispatch(actionCreator(...args));
}
function bindActionCreators(actionCreator, dispatch) {
    //actionCreators 可以是一个普通函数或者是一个对象
    if(typeof actionCreator === 'function') {
        //如果是函数,返回一个函数,调用时,dispatch 这个函数的返回值
        bindActionCreator(actionCreator, dispatch);
    }else if(typeof actionCreator === 'object') {
        //如果是一个对象,那么对象的每一项都要都要返回 bindActionCreator
        let boundActionCreators = {}
        for(let key in actionCreator) {
            boundActionCreators[key] = bindActionCreator(actionCreator[key], dispatch);
        }
        return boundActionCreators;
    }
}

Когда используешь:

let counter = bindActionCreators(counter, store.dispatch);
//派发时
counter.add(number);
counter.minus(number);

Здесь это не кажется слишком упрощенным, и это будет проанализировано позже.react-redux, мы объясним, зачем нам нужна эта служебная функция.

До сих пор нашreduxВ основном написано. а такжеreduxПо сравнению с исходным кодом все еще есть некоторые отличия, такие как некоторая проверка иcreateStoreкоторый предоставилreplaceReducerметод иcreateStoreМы не упомянули второй и третий параметры , но вы можете понять это, взглянув немного на код, поэтому я не буду раскрывать их здесь один за другим.

Ссылка на ссылку

  1. Маленькая книга React.js
  2. Китайская документация REDUX
  3. Полностью понять редукцию (реализовать редукцию с нуля)

Нет общественного беспокойства, добавление группы технического обмена