[React Series] Handwriting redux from scratch

  Front end, react.js, redux

After reading this article, you can also implement a redux.

clipboard.png

The code corresponding to this article is as follows:https://github.com/YvetteLau/ …It is suggested that firstcloneCode, and then read this article against the code.

1.ReduxWhat is it?

ReduxYesJavaScriptThe state container provides predictable state management.ReduxIn addition to andReactBesides being used together, other interface libraries are also supported.ReduxThe body is small and small, only2KB. Here we need to be clear:ReduxAndReactBetween, there is no strong binding relationship. This article aims to understand and implement aReduxBut it will not involvereact-redux(It is enough to understand one knowledge point at a time.react-reduxWill appear in the next article).

2. Realize one from scratchRedux

Let’s forgetReduxThe concept of, starting with an example, usingcreate-react-appCreate a project:toredux.

Code directory:myredux/to-reduxChina.

willpublic/index.htmlInbodyAmend to read as follows:

<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>

clipboard.png

The function we want to achieve is shown in the above figure. When clicking the button, we can modify the color of the font of the entire application.

Modifysrc/index.jsThe following (code: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();
}

This application is very simple, but it has a problem:stateIs a shared state, but anyone can modify it, once we arbitrarily modify this state, it can lead to errors, for example, inrenderHeaderInside, setstate = {}, easy to cause unexpected errors.

However, most of the time, we do need to share the status, so we can consider setting some thresholds. For example, we have agreed that we cannot directly modify the global status, and we must modify it through a certain route. To this end, we define achangeStateFunction, which is responsible for modifying the global state.

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

We agreed that only throughchangeStateTo modify the state, it accepts a parameteractionA that containstypeThe ordinary object of the field,typeThe field is used to identify your type of operation (i.e. how to modify the status).

We want to click on the button to change the font color of the entire application.

//在 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);
}

Pull away from store

Although we have now agreed on how to modify the status, howeverstateIs a global variable, we can easily modify it, so we can consider turning it into a local variable and defining it inside a function (createStoreHowever, it needs to be used externally.stateSo we need to provide a methodgetState()In order that we maycreateStoreGetstate.

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

Now, we can passstore.getState()Method to get the state (what needs to be explained here is that,stateIt is usually an object, so this object can be directly modified externally, but if it is deeply copiedstateReturn, then it must not be modified externally, given thatreduxSource code is directly returnedstate, here we also do not make a deep copy, after all, cost performance).

It is not enough to just obtain the state. We also need to have methods to modify the state. Now the state is a private variable. We must also put the methods to modify the state into thecreateStore, and expose it to external use.

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

Now,index.jsThe code in becomes the following (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());

Although, we are pulling away nowcreateStoreMethod, but obviously this method is not universal at all.stateAndchangeStateMethods are defined increateStoreChina. In this case, other applications cannot reuse this mode.

changeStateThe logic of is supposed to be defined externally, because the logic of modifying the state must be different for each application. We stripped this part of the logic to the outside and renamed itreducer(suppress ask why callreducerThe reason for asking is to make peace withreduxKeep consistent).reducerWhat is it, to put it bluntly, is based onactionTo calculate the new state. Because it is not increateStoreInternally defined, not directly accessiblestateSo we need to pass the current state to it as a parameter. As follows:

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

CreateStore evolution

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

Different applicationsstateIt must be different, we willstateThe value of is defined in thecreateStoreThe interior must be unreasonable.

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

Attention, everyonereducerThe definition of is to directly return to the old state when encountering unrecognized actions. Now, we use this to return to the initial state.

If you want tostateThere is an initial state, actually very simple, we will be the initialstateThe initialization value of is asreducerThe default value of the parameter for thecreateStoreTo distribute one inreducerIf you don’t understand, you can do it. suchgetStateOn the first call, you can get the default value of the state.

CreateStore evolution version 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
    }
}

Now thiscreateStoreIt can be used everywhere, but do you feel that every timedispatchAfter that, all manuallyrenderApp()It seems stupid. In the current application, it is called twice. If it needs to be modified 1000 timesstateDo you call it 1,000 times manually?renderApp()?

Can you simplify it? Called automatically every time the data changes.renderApp(). Of course, we can’trenderApp()Write increateStore()ThedispatchBecause in other applications, the function name may not be calledrenderApp()And may not only triggerrenderApp(). It can be introduced herePublish subscription modeTo notify all subscribers when the state changes.

CreateStore evolution version 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
    }
}

At this point, one of the simplestreduxIt’s already been created,createStoreYesreduxThe core of. Let’s use this condensed versionreduxRewrite our code,index.jsThe contents of the file are updated as follows (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()));

If we want to finish clicking nowPinkAfter that, the font color cannot be modified, so we can also unsubscribe:

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

By the way:reducerIt is a pure function (if you don’t know the concept of pure function, consult the data yourself), it receives the previousstateAndactionAnd returns a newstate. Don’t ask why.actionThere must betypeField, this is just a convention (reduxThis is how it was designed)

Legacy: WhyreducerBe sure to return a new onestate, rather than directly modifystateWhat? Welcome to leave your answer in the comment area.

In front of us we deduced step by stepreduxNow let’s review the core code ofreduxThe design idea of:

Reduxdesign philosophy

  • ReduxThe entire application state (state) to a place (usually we call itstore)
  • When we need to modify the status, we must distribute (dispatch) Oneaction(actionIt’s one withtypeThe object of the field)
  • Special state processing functionreducerReceive oldstateAndactionAnd returns a newstate
  • viasubscribeSet up subscriptions and notify all subscribers each time a distribution action is made.

We now have a basic versionreduxHowever, it still cannot meet our needs. Our usual business development is not as simple as the example written above, so there will be a problem:reducerFunctions can be very long becauseactionThere will be many types of. This is definitely not conducive to code writing and reading.

Imagine that there are 100 kinds of businesses in your business.actionNeed to deal with, write this one hundred cases in a.reducerNot only is the writing disgusting, but the colleagues who maintain the code later also want to kill people.

Therefore, we had better write it separately.reducer, and then toreducerTo merge. Please welcome ourcombineReducers(and)reduxThe name of the library remains the same)

combineReducers

First of all, we need to be clear:combineReducersJust a tool function, as we said earlier, it will be multiplereducerMerge into onereducer.combineReducersWhat is returned isreducerThat is to say, it is a higher order function.

We will still use an example to illustrate, althoughreduxNot necessarily withreactCooperation, but in view of its relationship withreactCooperation is most suitable, here, in order toreactCode for example:

This time, in addition to the above display, we added a counter function (usingReactRefactoring = = >to-redux2):

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

Obviously, the modification theme and the counter can be separated by differentreducerTo deal with it is a better choice.

store/reducers/counter.js

State responsible for handling counters.

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

State, which is responsible for modifying the theme color.

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;
    }
}

Each ..reducerOnly responsible for managing the overall situationstatePart of its responsibility. Each ..reducerThestateThe parameters are different and correspond to the part it manages.stateData.

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)
    }
}

appReducerAfter the mergerreducerBut whenreducerMore often, this writing is also cumbersome, so we write a tool function to generate thisappReducer, we named this tool functioncombineReducers.

Let’s try writing this tool functioncombineReducers:

Ideas:

  1. combineReducersReturnreducer
  2. combineReducersThere are multiple references toreducerThe object of the composition
  3. Each ..reducerOnly deal with the globalstateI am responsible for my own part
//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;
    }
}

SonreducerWill be responsible for returnstateThe default value of. For example, in this example,createStoreDispatch({type:@@redux/__INIT__${Math.random()}}), and passed to thecreateStoreThe truth iscombineReducers(reducers)Function returnedcombination.

According tostate=reducer(state,action),newState.theme=theme(undefined, action),newState.counter=counter(undefined, action),counterAndthemeTwo childrenreducerReturn separatelynewState.themeAndnewState.counterThe initial value of the.

Use thiscombineReducersCan be rewrittenstore/reducers/index.js

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

We wrote itcombineReducersAlthough it seems to be able to meet our needs, it has one disadvantage: it returns a new one every time.stateObject, which causes meaningless re-rendering when the data does not change. Therefore, we can judge the data and return the original data when there is no change in the data.stateJust.

Combinatorial combineReducers evolutionary edition

//代码中省略了一些判断,默认传递的参数均是符合要求的,有兴趣可以查看源码中对参数合法性的判断及处理
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

Official documentsAboutapplyMiddlewareThe explanation is very clear, the following contents also refer to the contents of the official documents:

Logging

Consider a small problem, if we want to print it out in the console before every state change.stateSo how do we do it?

The simplest is:

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

Of course, this method is definitely not desirable. If we distribute it 100 times in our code, we cannot write it 100 times. Since it is printing when the status changesstate, that is to say, indispatchPreviously printedstate, then we can rewritestore.dispatchMethod, print before distributionstateJust.

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

Crash information

Suppose we don’t just need to printstate, you also need to print out the error message when there is an exception in distribution.

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

However, if we have other requirements, we need to constantly revise them.store.dispatchMethod, resulting in this part of the code is difficult to maintain.

So we need separationloggerMiddlewareAndexceptionMiddleware.

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;

We know, a lotmiddlewareAll provided by a third party, thenstoreMust be passed as a parameter tomiddleware, further rewrite:

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

//使用
store.dispatch = exceptionMiddleware(store)(action);

There is still a small problem.exceptionMiddlewarehit the targetloggerMiddlewareIt is written to death, which is certainly unreasonable. We hope that this is a parameter, so it is flexible to use. There is no reason onlyexceptionMiddlewareNeed to be flexible, regardless ofloggerMiddlewareTo be further rewritten as follows:

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

Now, we already have GMmiddlewareThe writing format of the.

middlewareReceived onenext()ThedispatchFunction and returns adispatchFunction, the returned function will be taken as the nextmiddlewareThenext()

However, there is a small problem. When there is a lot of middleware, the code for using middleware becomes very complicated. To this end,reduxOne is providedapplyMiddlewareThe tool function of.

As we can see from the above, what we need to change in the end is actuallydispatchSo we need to rewrite itstore, return modifieddispatchAfter the methodstore.

Therefore, we can clarify the following points:

  1. applyMiddlewareThe return value isstore
  2. applyMiddlewareIt must be acceptedmiddlewareAs a parameter
  3. applyMiddlewareTo accept{dispatch, getState}As a participant, howeverreduxSource code into the reference iscreateStoreAndcreateStoreI think it is not necessary to create a new one outsidestoreAfter all, created externallystoreIn addition to being passed as a parameter into a function, it has no other effect, so it is better to put thecreateStoreAndcreateStoreParameters to be used are passed in.
//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
    }
}

The above is onemiddlewareBut we know that,middlewareIt may be one or more, and we mainly need to solve more than onemiddlewareThe problem of, further rewrite.

//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
    }
}

I don’t know if everyone understands the abovemiddles.reduceRight, the following detailed explanation for everyone:

/*三个中间件*/
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)));

Observe the abovemiddle1(middle2(middle3(store.dispatch)))If we putmiddle1,middle2,middle3As each item of the array, if you are familiar with the API of the array, you can think ofreduceIf you are not familiar with itreduce, you can viewMDN document.

//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)))

Read itreduxThe source code of the students, may know that the source code is to provide a.composeFunction, andcomposeNot used in functionreduceRight, but instead uses thereduceSo the code is slightly different. But the analysis process is still the same.

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)));
}

AboutreduceIt is suggested that the writing style be like the one above.reduceRightSimilarly, conduct an analysis

UsecomposeTool function rewriteapplyMiddleware.

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

reduxIt also provides us withbindActionCreatorsTool function, this tool function code is very simple, we seldom use it directly in the code.react-reduxWill be used in. Here, briefly explain:

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

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

export default counter;

At the time of distribution, we need to write like this:

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

store.dispatch(counter.add());

Of course, we can also write our actionCreator as follows:

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

When distributing, need to write like this:

store.dispatch(add(number));

The above codes have one thing in common, that is, they are allstore.dispatchSend out an action. So we can consider writing a function that willstore.dispatchAndactionCreatorBind them together.

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
        const boundActionCreators = {}
        for(let key in actionCreator) {
            boundActionCreators[key] = bindActionCreator(actionCreator[key], dispatch);
        }
        return boundActionCreators;
    }
}

In use:

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

It doesn’t seem that there is too much simplification here, but it will be analyzed later.react-reduxWhen, will explain why this tool function is needed.

At this point, myreduxIt has been basically written. AndreduxCompared with the source code of, there are still some differences, such ascreateStoreProvidedreplaceReducerMethods, andcreateStoreThe second and third parameters of the are not mentioned, so you can understand them by looking at the code a little bit, and they will not be expanded here.

Reference link

  1. React.js small book
  2. Redux chinese document
  3. Fully understand redux (implementing one redux from zero)

clipboard.png