action,reducer的生成函数

238 阅读3分钟

“我报名参加金石计划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的功能。