来看一个经常遇到的功能,就是拆分Reducer
。
在一个应用的state
中,很多时候存放了彼此业务逻辑没有直接关系的数据,比如看如下的一个store
:
export const store = createStore(changeReducer, {
value: 1,
theme: "dark",
names: ['cony', 'sesame', 'saner', 'sitong']
});
这其中的value
,theme
,names
对应的是没有直接关系的数据。
目前针对这些数据,如果不能拆分Reducer
,那么针对这三个数据的各种action
的判断要写在同一个Reducer
中,会让代码变得比较乱。
拆分Reducer
需要将Reducer进行适当的拆分,来看如何操作。
准备工作
先来将每个数据对应的action
和actionCreator
写好。对于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),
});
说白了就一句话,抽象层级的提高可以通过添加一个层次来搞定。