Redux介绍之Reducer

作者: 张歆琳 | 来源:发表于2017-05-13 00:39 被阅读287次

    上一篇中的Action只是个数据的载体,用于告知Reducer发生了什么事情,真正搞事情的还得靠Reducer,在Reducer里更新Store里的state。本篇就介绍一下Reducer,源码已上传Github,第三篇源代码请参照src/originReduxReducer和src/originReduxCombineReducer文件夹。

    Reducer应该是个纯函数,即只要传入相同的参数,每次都应返回相同的结果。不要把和处理数据无关的代码放在Reducer里,让Reducer保持纯净,只是单纯地执行计算。

    Reducer接收两个参数:旧的state和Action,返回一个新的state。即(state, action) => newState。有两个注意点:一是首次执行Redux时,你需要给state一个初始值。二是根据官网的说明,Reducer每次更新状态时需要一个新的state,因此不要直接修改旧的state参数,而是应该先将旧state参数复制一份,在副本上修改值,返回这个副本。

    第一点的板式语法是在Reducer的函数声明里,用es6的默认参数给state赋初值。第二点的板式语法是用es6的结构赋值将旧state复制一份:

    return {
         ...state,
        // 更新state中的值
    };
    

    在第二篇的源代码基础上继续修改源代码,将Reducer独立到reducers目录中,目录结构变为:


    reducers/number.js:

    import * as constant from '../configs/action';
    
    const initialState = {
        number: 0,
    };
    
    export default (state = initialState, action) => {
        switch (action.type) {
            case constant.INCREMENT:
                return {
                    ...state,
                    number: state.number + 1,
                };
            case constant.DECREMENT:
                return {
                    ...state,
                    number: state.number - 1,
                };
            case constant.CLEAR_NUM:
                return {
                    ...state,
                    number: 0,
                };
            default:
                return state;
        }
    };
    

    entries/originReduxReducer.js:

    // 改前
    const reducer = (state, action) => {
        if (typeof state === 'undefined') {
            return 0;
        }
    
        switch (action.type) {
            case constant.INCREMENT:
                return state + 1;
            case constant.DECREMENT:
                return state - 1;
            case constant.CLEAR_NUM:
                return 0;
            default:
                return state;
        }
    };
    
    const store = createStore(reducer);
    
    // 改后
    import reducer from '../reducers/number';
    
    const store = createStore(reducer);
    

    最终结果和前两篇是一样的,如下图数字会跟随点击的按钮发生变化。例子本身的结果不重要。重要的是代码的结构更加工程化。


    上一篇Action有个Action Creator,Reducer也有个Reducer Creator概念,用switch-case比较Action.type代码太low了。这里的low不是指用es6的语法就高大上了,而是指代码会不够清晰,代码是写给人看的,顺便让机器跑一下。我们用Reducer Creator改写一下上面的Reducer代码。

    抽出个lib/common.js,里面定义个createReducer共同函数:

    export const createReducer = (initialState, handlers) => {
        return (state = initialState, action) => {
            if (handlers.hasOwnProperty(action.type)) {
                return handlers[action.type](state, action);
            } else {
                return state;
            }
        }
    };
    

    如果你对函数式编程不熟悉,我啰嗦几句解释一下。createReducer本质上是返回一个函数对象。返回的匿名函数签名和Reducer函数签名是一样的,等于是封装了Reducer。在匿名函数内匹配Action.type,并返回一个和Reducer函数签名一样的另一个匿名函数。如果匹配不到Action.type,就返回state,这意味着你在写业务代码的Reducer时,甚至都不用考虑switch-case里default的问题。不知道我解释清楚了没有,不明白的话,多看几遍就能睡着了…

    现在Reducer里可以不用switch-case,用createReducer专注于业务逻辑了:

    import * as constant from '../configs/action';
    import { createReducer } from '../lib/common';
    
    const initialState = {
        number: 0,
    };
    
    export default createReducer(initialState, {
        [constant.INCREMENT]: (state, action) => {
            return {
                ...state,
                number: state.number + 1,
            };
        },
        [constant.DECREMENT]: (state, action) => {
            return {
                ...state,
                number: state.number - 1,
            };
        },
        [constant.CLEAR_NUM]: (state, action) => {
            return {
                ...state,
                number: 0,
            };
        },
    });
    

    Reducer既然是用于根据业务逻辑更新state,那如何切分业务是个问题。Redux基于此,提供了combineReducers方法,可以将多个Reducer合并成一个。参数是多个Reducer的key-value对象:

    combineReducers({
        reducer1: myReducer1,
        reducer2: myReducer2,
    });
    

    但通常会选择用Reducer名直接作为key,因此可以写成:

    combineReducers({
        myReducer1,
        myReducer2,
    });
    

    例如,我们为页面增加一个和数字不同的业务,点击按钮显示alert提示:


    目录结构更新为:


    alert的Action部分请自行参照源代码,不赘述。lib/common.js里封装着上面介绍过的createReducer方法,不赘述。reducers目录里number.js的代码不变,alert.js的代码结构同number.js,不赘述。

    reducers/index.js:

    import { combineReducers } from 'redux';
    import changeNumber from './number';
    import toggleAlert from './alert';
    
    export default combineReducers({
        changeNumber,
        toggleAlert,
    });
    

    现在Store里的state的结构变成:


    读取state值时:

    store.getState().changeNumber.number
    store.getState().toggleAlert.showAlert
    

    很简单,combineReducers就这点内容。补充一句,combineReducers并没规定只能连接到顶层Reducer里,你可以根据实际的业务逻辑封装出任意层级的Reducer。这样业务代码的封装性和可读性会变的更好。

    相关文章

      网友评论

        本文标题:Redux介绍之Reducer

        本文链接:https://www.haomeiwen.com/subject/mzotxxtx.html