Redux(二):组织reducer

2020-06-01 14:29:47 浏览数 (1)

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的功能,丰富了我们的应用。

0 人点赞