使用命名空间复用 Reducer 逻辑

2019-12-03 16:24:15 浏览数 (1)

本文作者:IMWeb jaychen 原文出处:IMWeb社区 未经同意,禁止转载

本文作者:IMWeb howenhuo 原文出处:IMWeb社区 未经同意,禁止转载

常见 Reducer 冗余

在使用 Redux 开发应用的过程中,我们经常会复制黏贴一些相似的 reducer

举个例子,有两个页面,它们都包含一个数据列表,它们都含有相同的状态

代码语言:javascript复制
const page1State = {
  loading: false, // 列表加载态(菊花)
  retcode: 0, // 列表CGI返回码
  page: 0, // 列表当前页码
  count: 10, // 列表每页数量
  pageTotal: 0, // 订单列表总共分页数量
  list: [], // 列表
  sort: -1 // 倒叙排序
};
const page2State = {
  loading: false, // 列表加载态(菊花)
  retcode: 0, // 列表CGI返回码
  page: 0, // 列表当前页码
  count: 10, // 列表每页数量
  pageTotal: 0, // 订单列表总共分页数量
  list: [], // 列表
  title: "" // 标题
};

接着我们可能会编写这样的 reducer

代码语言:javascript复制
function page1Reducer(state = page1State, { type, data }) {
  switch (type) {
    case PAGE1_LOADING_LIST: {
      return Object.assign({}, state, {
        loading: true
      });
    }
    case PAGE1_GET_LIST: {
      return Object.assign({}, state, {
        loading: false,
        page: data.page,
        pageTotal: data.pageTotal,
        list: data.list || []
      });
    }
    case PAGE1_ERR_LIST: {
      return Object.assign({}, state, {
        loading: false,
        retcode: data.retcode
      });
    }
    default:
      return state;
  }
}

function page2Reducer(state = page2State, { type, data }) {
  switch (type) {
    case PAGE2_LOADING_LIST: {
      return Object.assign({}, state, {
        loading: true
      });
    }
    case PAGE2_GET_LIST: {
      return Object.assign({}, state, {
        loading: false,
        page: data.page,
        pageTotal: data.pageTotal,
        list: data.list || []
      });
    }
    case PAGE2_ERR_LIST: {
      return Object.assign({}, state, {
        loading: false,
        retcode: data.retcode
      });
    }
    default:
      return state;
  }
}

区分不同的 action type 主要是避免 reduer 错误处理。

但这样看上去是否很冗余?那么是否可以只用一个 list reducer,就能处理这种重复的流程呢?

使用命名空间

首先我们将相同的状态抽取出来

代码语言:javascript复制
const listState = {
  loading: false, // 列表加载态(菊花)
  retcode: 0, // 列表CGI返回码
  page: 0, // 列表当前页码
  count: 10, // 列表每页数量
  pageTotal: 0, // 订单列表总共分页数量
  list: [] // 列表
};

const page1State = {
  ...listState,
  //其他字段
  sort: -1 // 倒叙排序
};

const page2State = {
  ...listState,
  //其他字段
  title: "" // 标题
};

接着把前面两个 reducer 相同的部分提取出来

代码语言:javascript复制
function listReducer(reducerNamespace) {
  return (state, { namespace, type, data }) => {
    // 初始调用或者命名空间不一致的都不做处理
    if (state === undefined || reducerNamespace !== namespace) {
      return state;
    }
    switch (type) {
      case LOADING_LIST: {
        return Object.assign({}, state, {
          loading: true
        });
      }
      case GET_LIST: {
        return Object.assign({}, state, {
          loading: false,
          page: data.page,
          pageTotal: data.pageTotal,
          list: data.list || []
        });
      }
      case ERR_LIST: {
        return Object.assign({}, state, {
          loading: false,
          retcode: data.retcode
        });
      }
      default:
        return state;
    }
  };
}

这样原来的 Reducer 就简化成如下

代码语言:javascript复制
const page1List = listReducer('PAGE1')
function page1Reducer(state = page1State, action) {
  state = page1List(state, action);
  switch (action.type) {
    // 其他逻辑
    default:
      return state;
  }
}

const page2List = listReducer('PAGE2')
function page2Reducer(state = page2State, action) {
  state = page2List (state, action);
  switch (action.type) {
    // 其他逻辑
    default:
      return state;
  }
}

最后我们在 dispatch 时,增加 namespace 字段就能指定处理哪一个列表的数据了

代码语言:javascript复制
store.dispatch({
  namespace: "PAGE1",
  type:GET_LIST,
  data: {
    page: 1,
    pageTotal: 10,
    list: [1, 2, 3, 4, 5]
  }
});

store.dispatch({
  namespace: "PAGE2",
  type: ERR_LIST,
  data: {
    retcode: 404
  }
});

使用高阶函数抽象 reducer

虽然上面在 pageReducer 中已经抽象出相同的 listReducer,但写法上不是很优雅,我们再实现一个 composeReducers

代码语言:javascript复制
function composeReducers(...reducers) {
  return (state, action) => {
    if (reducers.length === 0) {
      return state;
    }
    const last = reducers[reducers.length - 1]; 
    const rest = reducers.slice(0, -1);
    return rest.reduceRight(
      (enhanced, reducer) => reducer(enhanced, action),
      last(state, action)
    );
  };
}

利用这个 composeReducers 可以把原来 pageReducer 中耦合的 listReducer 分离出来,并且可以轻松的组合多个 Reducer

代码语言:javascript复制
export default combineReducers({
  page1Reducer: composeReducers(page1Reducer, listReducer("PAGE1")),
  page2Reducer: composeReducers(page2Reducer, listReducer("PAGE2"))
});

例子源码

codesandbox

参考

Reducer 逻辑复用

重用 Redux 中的 reducer

0 人点赞