【React系列】从零开始实现Redux

本篇文章会从零开始编写 Redux,若是你对 Redux 的使用和源码已经很是熟悉,那么你只须要粗略浏览便可,若是你还不太清楚,那么跟着本篇文章一块儿来实现你的 Redux 吧。知其然,知其因此然。javascript

本篇文章对应的代码在: github.com/YvetteLau/B… ,建议先 clone 代码,对照代码阅读本文。html

1. Redux 是什么?

ReduxJavaScript 状态容器,提供可预测化的状态管理。Redux 除了和 React 一块儿用外,还支持其它界面库。Redux 体小精悍,仅有 2KB。这里咱们须要明确一点:ReduxReact 之间,没有强绑定的关系。本文旨在理解和实现一个 Redux,可是不会涉及 react-redux(一次深刻理解一个知识点便可,react-redux 将出如今下一篇文章中)。前端

2. 从零开始实现一个 Redux

咱们先忘记 Redux 的概念,从一个例子入手,使用 create-react-app 建立一个项目: to-reduxjava

代码地址: myredux/to-redux 中。react

public/index.htmlbody 修改成以下:git

<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>
复制代码

咱们要实现的功能如上图所示,在点击按钮时,可以修改整个应用的字体的颜色。github

修改 src/index.js 以下(代码: to-redux/src/index1.js):redux

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 函数,全局状态的修改均由它负责。app

//在 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);
}
复制代码

抽离 store

尽管如今咱们约定了如何修改状态,可是 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 方法,可是显然这个方法一点都不通用,statechangeState 方法都定义在了 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 进化版

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 进化版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 进化版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 已经建立好了,createStoreredux 的核心。咱们来使用这个精简版的 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 是一个纯函数(纯函数的概念若是不了解的话,自行查阅资料),它接收先前的 stateaction,并返回新的 state。不要问为何 action 中必定要有 type 字段,这仅仅是一个约定而已(redux 就是这么设计的)

遗留问题:为何 reducer 必定要返回一个新的 state,而不是直接修改 state 呢。欢迎在评论区留下你的答案。

前面咱们一步一步推演了 redux 的核心代码,如今咱们来回顾一下 redux 的设计思想:

Redux 设计思想

  • Redux 将整个应用状态(state)存储到一个地方(一般咱们称其为 store)
  • 当咱们须要修改状态时,必须派发(dispatch)一个 action( action 是一个带有 type 字段的对象)
  • 专门的状态处理函数 reducer 接收旧的 stateaction ,并会返回一个新的 state
  • 经过 subscribe 设置订阅,每次派发动做时,通知全部的订阅者。

我们如今已经有一个基础版本的 redux 了,可是它还不能知足咱们的需求。咱们平时的业务开发不会像上面所写的示例那样简单,那么就会有一个问题: reducer 函数可能会很是长,由于 action 的类型会很是多。这样确定是不利于代码的编写和阅读的。

试想一下,你的业务中有一百种 action 须要处理,把这一百种状况编写在一个 reducer 中,不只写得人恶心,后期维护代码的同事更是想杀人。

所以,咱们最好单独编写 reducer,而后对 reducer 进行合并。有请咱们的 combineReducers(和 redux 库的命名保持一致) 闪亮登场~

combineReducers

首先咱们须要明确一点:combineReducers 只是一个工具函数,正如咱们前面所说,它将多个 reducer 合并为一个 reducercombineReducers 返回的是 reducer,也就是说它是一个高阶函数。

咱们仍是以一个示例来讲明,尽管 redux 不是非得和 react 配合,不过鉴于其与 react 配合最为适合,此处,以 react 代码为例:

这一次除了上面的展现之外,咱们新增了一个计数器功能( 使用 React 重构 ===> to-redux2):

//如今咱们的 state 结构以下:
let state = {
    theme: {
        color: 'blue'
    },
    counter: {
        number: 0
    }
}
复制代码

显然,修改主题和计数器是能够分割开得,由不一样的 reducer 去处理是一个更好的选择。

store/reducers/counter.js

负责处理计数器的state。

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。

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 中它负责的一部分。每一个 reducerstate 参数都不一样,分别对应它管理的那部分 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;
    }
}
复制代码

reducer 将负责返回 state 的默认值。好比本例中,createStoredispatch({type:@@redux/INIT${Math.random()}}),而传递给 createStore 的是 combineReducers(reducers) 返回的函数 combination

根据 state=reducer(state,action)newState.theme=theme(undefined, action), newState.counter=counter(undefined, action)countertheme 两个子 reducer 分别返回 newState.themenewState.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次,咱们不可能这样写一百次。既然是状态改变时打印 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 方法,最后致使这个这部分代码难以维护。

所以咱们须要分离 loggerMiddlewareexceptionMiddleware.

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 都是第三方提供的,所以须要将 dispatchgetState 确定是须要做为参数传递给 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})(action);
复制代码

如今还有一个小小的问题,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 函数,返回的函数会被做为下一个 middlewarenext()

可是有一个小小的问题,当中间件不少的时候,使用中间件的代码会变得很繁琐。为此,redux 提供了一个 applyMiddleware 的工具函数。

上面咱们可以看出,其实咱们最终要改变的就是 dispatch,所以咱们须要重写 store,返回修改了 dispatch 方法以后的 store.

因此,咱们能够明确如下几点:

  1. applyMiddleware 返回值是 store
  2. applyMiddleware 确定要接受 middleware 做为参数
  3. applyMiddleware 要接受 {dispatch, getState} 做为入参,不过 redux 源码中入参是 createStorecreateStore 的入参,想一想也是,没有必要在外部建立出 store,毕竟在外部建立出的 store 除了做为参数传递进函数,也没有其它做用,不如把 createStorecreateStore 须要使用的参数传递进来。
//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.dispatchactionCreator 绑定起来。

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. 彻底理解 redux(从零实现一个 redux)

关注公众号,加入技术交流群

相关文章
相关标签/搜索