“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第2篇文章,点击查看活动详情”
在上一篇中,我们使用redux-saga写了一个较为完整的请求过程,那么在实际使用时我们每次请求都会使用到这一流程,因此我们自然会想到将这一流程简化。首先我们就可以从Action入手。
Actions
Actions 是用来描述在 app 中发生了什么的普通对象,并且是描述突变数据意图的唯一途径。它一般长这样:
{ type: 'START_FETCH' }
{ type: 'FETCH_SUCCESS', data: { ... } }
{ type: 'FETCH_FAILED', error: 'error message' }
一般我们都将其中的type定义为string常量放在ActionType文件中:
export const START_FETCH = 'START_FETCH'
export const FETCH_SUCCESS = 'FETCH_SUCCESS'
export const FETCH_FAILED = 'FETCH_FAILED'
这样做优势就是可以将所有action type放在同一个文件中,添加删除action都是一目了然的。这能帮助团队中的所有人及时追踪新功能的范围与实现,
这对大型项目来说十分重要。
另一个生成action对象的做法就是通过Action Creators,而不是在dispatch时内联他们:
// 一般不这样使用
dispatch({
type: FETCH_SUCCESS,
data: { ... }
});
// 取而代之的是这样
// actionCreators.js
export function fetchSuccess(data) {
return {
type: FETCH_SUCCESS,
data
};
}
// fetchDataPage.js
import { fetchSuccess } from './actionCreators';
// event handler 里的某处
dispatch(fetchSuccess({ ... }))
Action Creators 生成器
在项目中不可避免的action会有很多,那么对于上面的actionCreators我们也就需要不断重复的去创建了,最终往往生成多余的样板代码:
export function startFetch(callback) {
return {
type: START_FETCH,
callback
}
}
export function fetchSuccess(data) {
return {
type: FETCH_SUCCESS,
data
}
}
export function fetchFailed(error) {
return {
type: FETCH_FAILED,
error
}
}
我们可以维护一个用于生成action creator的函数,这个函数输入type和action的参数,返回一个action creator:
function makeActionCreator(type, ...argNames){
return function(...args){
let action = {type}
argNames.forEach((arg,index) => {
action[argNames[index]] = args[index]
})
return action
}
}
我们分析一下这个函数,首先入参为action的type和action的其他参数,返回一个action creator,并用fetchSuccess接收:
fetchSuccess = function(...args){
let action = {type}
argNames.forEach((arg,index) => {
action[argNames[index]] = args[index]
})
return action
}
这样看生成的type就是我们传入的type,我们传入几个参数就往action中添加几个参数,比如下面这样:
export const fetchSuccess = makeActionCreator(FETCH_SUCCESS, 'data','loading')
// 生成的action
{
type: 'FETCH_SUCCESS',
data,
loading
}
reducers
同样的reducers也是一样的问题,我们上面的actions对应的reducers应该写成下面这样:
const initState = {
data: {},
error: null
}
export default funciton FetchReducers(state = initState,action) {
switch (action.type){
case START_FETCH:
return Object.assign({},state,{})
case FETCH_SUCCESS:
return Object.assign({},state,{
data: action.payload
})
case FETCH_FAILED:
return Object.assign({},state,{
error: action.error
})
}
}
看起来我们写了switch,当actions很多时代码就会变得繁琐起来,我们也可以使用一个单独的函数来解决这个问题
Reducers生成器
写一个函数将 reducers 表达为 action types 到 handlers 的映射对象。例如,如果想在 todos reducer 里这样定义:
function createReducer(initialState, handlers) {
return function reducer(state = initialState, action) {
if (handlers.hasOwnProperty(action.type)) {
return handlers[action.type](state, action);
} else {
return state;
}
}
}
export const fetchSuccess = createReducer([], {
[FETCH_SUCCESS](state, action) {
let data = action.data;
return [...state, data];
}
})
createReducer传入的参数为initState和一个以actionType为函数名的handlers函数:
[FETCH_SUCCESS](state, action) {
let data = action.data;
return [...state, data];
}
这个函数是从createReducer函数的第一个参数拿到的initState,从createReducer函数的第二个参数action拿到的action payload,
返回的是一个更新过的state,实际上这个函数就代替了我们在普通写法中:
return Object.assign({},state,{data: action.payload})
的这一部分。 而if (handlers.hasOwnProperty(action.type))的判断条件其实就是代替了switch。
到这里我们去dispatch一次action的时候,实际上就是去调用一次createReducer,完全代替了reducer的功能。