reducer是一个可预测的纯函数,接收2个参数:当前的state和action,然后返回更新后的state。
一、初始reducer
代码语言:javascript复制const initialState = {
visibilityFilter:"SHOW_ALL",
todos:[]
};
function appReducer(state=initialState,action){
switch (action.type){
case "SET_VISIBILITY_FILTER":{
return Object.assign({},state,{
visibilityFilter:action.filter
});
}
case "ADD_TODO":{
return Object.assign({},state,{
todos:state.todos.concat({
id:action.id,
text:action.text,
completed:false
})
})
}
case "TOGGLE_TODO":{
return Object.assign({},state,{
todos:state.todos.map(todo=>{
if(todo.id !== action.id){
return todo;
}
return Object.assign({},todo,{
completed:!todo.completed
})
})
})
}
case "EDIT_TODO":{
return Object.assign({},state,{
todos:state.todos.map(todo=>{
if(todo.id !== action.id){
return todo;
}
return Object.assign({},todo,{
text : action.text
})
})
})
}
default:{
return state;
}
}
}
这是一个比较简单的reducer,包含2个状态区域:todos和visibilityFilter,4个子条件语句对应的action.type分别为:SET_VISIBILITY_FILTER、ADD_TODO、TOGGLE_TODO、EDIT_TODO。在实际项目中,分支语句对应的action.type会非常多,如果写到一个reducer里这个函数会变的非常的臃肿,所以接下来需要对其进行逐步的拆分。
二、提取工具函数
期间大量的使用的Object.assign()方法,这是一个ES6函数用来创建一个新对象,可以将其提取成一个函数:
代码语言:javascript复制function updateObject(oldObject,newValues){
return Object.assign({},oldObject,newValues);
}
当我们更新todos中的某一个todo是总是需要去遍历替换,所以也可以提取成一个函数:
代码语言:javascript复制function updateItemInArray(array,itemId,callback){
array.map(item=>{
if(item.id !== itemId){
return item;
}
return callback(item);
})
}
那么主体的reducer就是这样子了:
代码语言:javascript复制function appReducer(state=initialState,action){
switch (action.type){
case "SET_VISIBILITY_FILTER":{
return updateObject(state,{
visibilityFilter:action.filter
});
}
case "ADD_TODO":{
return updateObject(state,{
todos:state.todos.concat({
id:action.id,
text:action.text,
completed:false
})
});
}
case "TOGGLE_TODO":{
return updateObject(state,{
todos:updateItemInArray(state.todos,action.id,todo=>{
return updateObject(todo,{
completed:!todo.completed
})
})
});
}
case "EDIT_TODO":{
return updateObject(state,{
todos:updateItemInArray(state.todos,action.id,todo=>{
return updateObject(todo,{
text:todo.text
})
})
});
}
default:{
return state;
}
}
}
三、提取case reducer
接着,将每一个case语句对应的逻辑封装成函数:
代码语言:javascript复制function setVisibilityFilter(state,action){
return updateObject(state,{
visibilityFilter:action.filter
});
}
function addTodo(state,action){
return updateObject(state,{
todos:state.todos.concat({
id:action.id,
text:action.text,
completed:false
})
});
}
function toggleTodo(state,action){
return updateObject(state,{
todos:updateItemInArray(state.todos,action.id,todo=>{
return updateObject(todo,{
completed:!todo.completed
})
})
});
}
function editTodo(state,action){
return updateObject(state,{
todos:updateItemInArray(state.todos,action.id,todo=>{
return updateObject(todo,{
text:todo.text
})
})
});
}
function appReducer(state=initialState,action){
switch (action.type){
case "SET_VISIBILITY_FILTER":return setVisibilityFilter(state,action);
case "ADD_TODO":return addTodo(state,action);
case "TOGGLE_TODO":return toggleTodo(state,action);
case "EDIT_TODO":return editTodo(state,action);
default:{return state;}
}
}
四、提取case reducer
这个例子中包含2种数据状态:visibilityFilter和todos,且每一个case的代码块值关心其中的一个数据状态,所以简单改造一下:
代码语言:javascript复制function setVisibilityFilter(visibilityState,action){
return action.filter;
}
function addTodo(todosState,action){
return todosState.concat({
id:action.id,
text:action.text,
completed:false
});
}
function toggleTodo(todosState,action){
return updateItemInArray(todosState,action.id,todo=>{
return updateObject(todo,{
completed:!todo.completed
})
});
}
function editTodo(todosState,action){
return updateItemInArray(todosState,action.id,todo=>{
return updateObject(todo,{
text:todo.text
})
})
}
4个函数分成2类,所以接着编写2个子reducer:
代码语言:javascript复制function visibilityReducer(visibilityState="SHOW_ALL",action){
switch (action.type){
case "SET_VISIBILITY_FILTER":return setVisibilityFilter(visibilityState,action);
default:return visibilityState;
}
}
function todosReducer(todos=[],action){
switch (action.type){
case "ADD_TODO":return addTodo(todos,action);
case "TOGGLE_TODO":return toggleTodo(todos,action);
case "EDIT_TODO":return editTodo(todos,action);
default:return todos;
}
}
那么我们的根reducer需要做的仅仅是返回一个包含2个属性visibilityFilter和todos的对象,对象的属性值就是上边2个子reducer的执行结果:
代码语言:javascript复制function appReducer(state=initialState,action){
return{
visibilityFilter:visibilityReducer(state.visibilityState,action),
todos:todosReducer(state.todos,action)
}
}
五、减少样式模板代码
其实现在基本已经大功告成了,如果能将子reducer里的switch语句再精简一下会看起来更简约。所以我们需要先编写一个createReducer函数来生成一个函数,这个生成的函数就是我们的子reducer:
代码语言:javascript复制function createReducer(initialState,handlers){
return function(state=initialState,action){
if(handlers.hasOwnProperty(action.type)){
return handlers[action.type](state,action);
}else{
return state;
}
}
}
那么之前的visibilityReducer、todosReducer就变成了这样子:
代码语言:javascript复制const visibilityReducer = createReducer("SHOW_ALL",{
SET_VISIBILITY_FILTER:setVisibilityFilter
});
const todosReducer = createReducer([],{
ADD_TODO:addTodo,
TOGGLE_TODO:toggleTodo,
EDIT_TODO:editTodo
});
其它地方都不用改动。
六、组合reducer
接着我们需要再优化一下根reducer,也就是appReducer。首先编写一个函数combineReducers来组合我们的子reducer:
代码语言:javascript复制function combineReducers(initialState,reducers){
return function(state=initialState,action){
return Object.entries(reducers).reduce((init,item)=>{
const key = item[0];
const callback = item[1];
init[key] = callback(state[key],action);
return init;
},{});
}
}
那么最终appReducer就是这样子了:
代码语言:javascript复制const appReducer = combineReducers(initialState,{
visibilityFilter:visibilityReducer,
todos:todosReducer
});
从redux中引入createStore创建store:
代码语言:javascript复制const store = createStore(combineReducers(initialState,{
visibilityFilter:visibilityReducer,
todos:todosReducer
}));
至此,大功告成~
Redux内置了combineReducers函数,与我们的功能本质上是相同的。Reducer本质上就是纯函数,每一次派发action都会导致Reducer的执行,而Reducer的内部通过条件语句下发到子reducer,最终计算出新的state状态树并更新store。接着依次执行通过subscribe注册的回调函数,那么这个回调函数就是关键了,如果都是同步函数,那放到一个数组中遍历依次执行即可,但如果是异步函数那就要用到接下来要讲的中间件了,可以说正是中间件系统极大的拓展了redux的功能,丰富了我们的应用。