【问题标题】:Redux normalised data using separate reducersRedux 使用单独的 reducer 规范化数据
【发布时间】:2019-05-14 07:56:55
【问题描述】:

我有一个 Posts 模型,它有一个 UserComments 通过阅读 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]
    }
}

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

的操作

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

使用 postsReducer 会导致

state.posts.users

相反,好的做法是这样的

state.users

【问题讨论】:

    标签: reactjs redux normalization


    【解决方案1】:

    创建单独的 reducer 并将它们组合起来:

    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 });
    

    【讨论】:

    • 这看起来真不错!我确实想知道,例如,如果点击“Post”单页 url,我们使用 FETCH_POST_REQUEST,但是如果没有加载帖子怎么办?因为当我们点击“帖子”概览页面时会加载“所有帖子”。
    • 我无法清楚地理解这个疑问。如果有一个单独的页面,它只获取一个帖子,您可以使用相同的操作。唯一的区别是action.payload.data 将是单个元素数组而不是一堆。如果获取的单页和概览页的帖子内容不同,那么您可能需要维护一个单独的 reducer,即。 postsDetail
    【解决方案2】:

    简单的解决方案:让你的 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)
    

    【讨论】:

      猜你喜欢
      • 2017-08-16
      • 2018-12-06
      • 1970-01-01
      • 1970-01-01
      • 2016-02-11
      • 2017-04-25
      • 1970-01-01
      • 2021-04-05
      • 2014-07-07
      相关资源
      最近更新 更多