使用单独的reducer还原标准化数据

时间:2018-12-13 06:27:22

标签: reactjs redux normalization

我有一个帖子模型,该模型具有用户评论 通过阅读redux文档,我了解到理想状态已被规范化,并使用如下键:

{
    "posts": {
        "byId": {
            1: {
                id: 1,
                name: "Post One",
                user: [10]
            },
            2: {
                id: 2,
                name: "Post Two",
                user: [11]
            }
        },
        "allIds": [1, 2]
    },
    "users": {
        "byId": {
            10: {username: "User One"},
            11: {username: "User Two"}
        },
        "allIds": [10, 11]
    }
}

我认为这是正确的状态。现在的问题是,如果我有一个操作获取所有帖子及其用户,评论...

  

使用normalizr可以将状态标准化以匹配上面的模型。   当我在postsReducer中收听FETCH_POSTS_SUCCESS时。如何在用户自己的根路径中添加用户?

使用postsReducer将导致

state.posts.users

相反,好的做法是这样

state.users

2 个答案:

答案 0 :(得分:1)

幼稚的解决方案:让您的userReducer收听FETCH_POSTS_SUCCESS,如果帖子列表中有任何用户,请更新其自己的状态。当然,这会污染逻辑,因为FETCH_POST_SUCCESS不再仅属于postReducer。

我可以建议2种替代解决方案:

一种是,如果您使用任何软件包,例如redux-thunk或redux-saga,则在成功提取帖子时调用次要效果。以下示例适用于redux-thunk

function fetchPosts() {
    return function(dispatch) {
        return fetchPostsAPICall()
            .then((posts) => {
                dispatch(fetchPostSuccess, posts)
                const users = getUsersFromPosts()
                dispatch(massUpdateUsers, users)
            })
    }
}

第二个正在使用中间件来监听FETCH_POST_SUCCESS,并在其中处理次要效果。以下示例使用redux-saga.takeEvery

function* handleFetchPostsSuccess({ type, payload }) {
    const users = getUsersFromPayload(payload)
    yield put({ type: 'MASS_UPDATE_USERS', users })

}

function* watchFetchPosts() {
    yield takeEvery(FETCH_POSTS_SUCCESS, handleFetchPostsSuccess)
}

...

const sagaMiddleware = createSagaMiddleware()
const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
)
sagaMiddleware.run(watchFetchPosts)

答案 1 :(得分:1)

创建单独的异径管并将其组合:

function normalizeData(data, initialValue = [], idKey = 'id') {
  return data.reduce(
    (accumulator, currentValue) => ({
      ...accumulator,
      [currentValue[idKey]]: currentValue,
    }),
    {},
  );
}

function mapIds(data, initialValue = [], idKey = 'id') {
  const ids = data.map(eachData => eachData[idKey]);
  return [...initialValue, ids];
}

function posts(state = {}, action) {
  switch (action.type) {
    case types.FETCH_POSTS_SUCCESS:
      return {
        byId: normalizeData(action.payload.data, state.byId),
        allIds: mapIds(action.payload.data, state.allIds),
      };
    default:
      return state;
  }
}

function users(state = {}, action) {
  switch (action.type) {
    case types.FETCH_USERS_SUCCESS:
      return {
        byId: normalizeData(action.payload.data, state.byId),
        allIds: mapIds(action.payload.data, state.allIds),
      };
    default:
      return state;
  }
}

export default combineReducers({ posts, users });

您还可以为这些创建辅助包装器,这些包装器可重复使用以轻松创建其他实体化简器:

const byIdReducerCreator = (actionType, idKey = 'id') => (state = {}, action) => {
  switch (action.type) {
    case types[actionType]:
      return normalizeData(action.payload.data, state, idKey);
    default:
      return state;
  }
};

const allIdReducerCreator = (actionType, idKey = 'id') => (state = [], action) => {
  switch (action.type) {
    case types[actionType]:
      return mapIds(action.payload.data, state, idKey);
    default:
      return state;
  }
};

const posts = combineReducers({
  byId: byIdReducerCreator('FETCH_POSTS_SUCCESS'),
  allIds: allIdReducerCreator('FETCH_POSTS_SUCCESS'),
});

const users = combineReducers({
  byId: byIdReducerCreator('FETCH_USERS_SUCCESS', 'someOtherId'),
  allIds: allIdReducerCreator('FETCH_USERS_SUCCESS', 'someOtherId'),
});

export default combineReducers({ posts, users });