Redux 04 拆分Reducer与使用Selector

Redux 04 拆分Reducer与使用Selector

继续一点一点看Redux

来看一个经常遇到的功能,就是拆分Reducer
在一个应用的state中,很多时候存放了彼此业务逻辑没有直接关系的数据,比如看如下的一个store

export const store = createStore(changeReducer, {
    value: 1,
    theme: "dark",
    names: ['cony', 'sesame', 'saner', 'sitong']
});

这其中的valuethemenames对应的是没有直接关系的数据。

目前针对这些数据,如果不能拆分Reducer,那么针对这三个数据的各种action的判断要写在同一个Reducer中,会让代码变得比较乱。

拆分Reducer

需要将Reducer进行适当的拆分,来看如何操作。

准备工作

先来将每个数据对应的actionactionCreator写好。对于actionType就每种设置一个类型:

const actionTypes = {
    valueType: "VALUE_TYPE",
    themeType: "THEME_TYPE",
    nameType: "NAME_TYPE",
}

export const valueTypeCreator = (number = 1) => {
    return {
        type: actionTypes.VALUE_TYPE,
        number,
    }
}

export const themeTypeCreator = (theme='normal') => {
    return {
        type: actionTypes.THEME_TYPE,
        theme,
    }
}

export const nameTypeCreator = (newName = "") => {
    return {
        type: actionTypes.NAME_TYPE,
        newName,
    }
};

针对每个数据编写单独的Reducer

这里的核心,是要注意编写的Reducer是针对state中的一部分,而不是完整的state。虽然函数签名没有变,但实际传入函数内部的state是各自对应的那一部分。

看一个例子,针对上边的VALUE_TYPE来编写Reducer

const valueReducer = (state,action) =>{

    if (action.type === actionTypes.VALUE_TYPE) {
        return state + action.number;
    } else {
        return state;
    }

}

这里为什么直接返回了state,是因为在这个函数中,state参数就是store.getState().value,也就是一个数值,返回也要返回同样的类型,所以这里要么直接返回自增1之后的数值,要么返回原来的数值。

根据这个路子,把剩下的也编写掉:

export const themeReducer = (state, action) => {

    if (action.type === actionTypes.THEME_TYPE) {
        return "light";
    } else {
        return state;
    }

};

export const nameReducer = (state, action) => {

    if (action.type === actionTypes.NAME_TYPE) {
        return state.concat('newnew');
    } else {
        return state;
    }

};

themeReducer对应的就是字符串的theme,而nameReducer要处理的就是names对应的数组。
要注意的是,这里没有给state赋初始值,此种情况下需要确保创建store的时候传入了初始化的state,否则会报错。如果不打算传入初始state,则在Reducer中设置默认值,相当于初始化state

组合Reducer

现在针对state各个部分的Reducer都有了,需要将其合并起来,这需要用到combineReducers函数:

import {combineReducers} from "@reduxjs/toolkit";
import {nameReducer, themeReducer, valueReducer} from "./SingleReducers";

const combinedReducer = combineReducers({
    value: valueReducer,
    theme: themeReducer,
    names: nameReducer,
});

export default combinedReducers;

这个组合后的combinedReducer就是要用来创建store的实际Reducer。这里要注意的是,传给combineReducers的是一个对象,对象中每一个键值对,键名是store里存储的总state中要让对应的Reducer处理的那个键,值则是对应的Reducer函数。

实际例子

这里使用一个很简单的操作,写三个按钮和三个输出的内容,用上一章的内容去绑定这些玩意,然后来看这个Reducer是不是可以正常工作。

创建store

直接使用上边的初始值和组合后的Reducer进行创建:

export const store = createStore(combinedReducer, {
    value: 10,
    theme: "daylight",
    names: ['saner', 'sitong']
});

之后编写按钮和绑定展示,是上一篇文章的套路了:

class ChangeValue extends Component {

    constructor(props) {
        super(props);
    }

    valueToProps = (state) => ({
        value: state.value,
    });

    themeToProps = (state) => ({
        value: state.theme,
    });

    namesToProps = (state) => ({
        value: state.names,
    });

    render() {

        const Result1 = connect(this.valueToProps, null)(MyResult);
        const Result2 = connect(this.themeToProps, null)(MyResult);
        const Result3 = connect(this.namesToProps, null)(MyResult);

        return (
            <div className="App">
                <MyButton text={"value"} onClick={() => store.dispatch(valueTypeCreator(1))}/>
                <Result1/>
                <MyButton text={"theme"}  onClick={() => store.dispatch(themeTypeCreator("dark"))}/>
                <Result2/>
                <MyButton text={"name"} onClick={() => store.dispatch(nameTypeCreator("new"))}/>
                <Result3/>

            </div>
        );
    }
}

三个按钮派发三种action,之后连接三个展示组件用于展示更新后的内容。

如此编写之后,点击三个按钮,就可以发现成功的更新了store中的内容。

一般来说,Reducer都应当进行合理的拆分再组合,组合起来的Reducer一般被称为rootReducer

Selector函数

这个名字听着很玄乎,选择器,还以为和CSS有点关系。实际上,这个函数是用来从state中提取数据的,还可以对数据进行一些处理再返回给值,同样是为了避免重复的读取和处理逻辑。

Selector函数的标准式样很简单,就是如下:

const selector = state => state.value

一般针对一个state,可以编写提取各个细节的selector函数,之后只需要复用这些函数就行,至于state当然就是通过store.getState()获取了。

例子

例子其实没什么必要,还是写两个好了。
在前边我们编写了几个mapStateToProps函数,其中直接使用了state.value之类的字样,现在编写几个selector函数并让这些mapStateToProps使用之:

export const selectValueFromState = (state) => (state.value);
export const selectThemeFromState = (state) => (state.theme);
export const selectNamesFromState = (state) => (state.names);

valueToProps = (state) => ({
    value: selectValueFromState(state),
});

themeToProps = (state) => ({
    value: selectThemeFromState(state),
});

namesToProps = (state) => ({
    value: selectNamesFromState(state),
});

说白了就一句话,抽象层级的提高可以通过添加一个层次来搞定。

LICENSED UNDER CC BY-NC-SA 4.0
Comment