首页 文章

如何添加/删除使用normalizr生成的redux存储?

提问于
浏览
32

查看README中的示例:

鉴于“坏”结构:

[{
  id: 1,
  title: 'Some Article',
  author: {
    id: 1,
    name: 'Dan'
  }
}, {
  id: 2,
  title: 'Other Article',
  author: {
    id: 1,
    name: 'Dan'
  }
}]

添加新对象非常容易 . 我所要做的就是这样

return {
  ...state,
  myNewObject
}

在减速机中 .

现在给出了“好”树的结构,我不知道应该如何处理它 .

{
  result: [1, 2],
  entities: {
    articles: {
      1: {
        id: 1,
        title: 'Some Article',
        author: 1
      },
      2: {
        id: 2,
        title: 'Other Article',
        author: 1
      }
    },
    users: {
      1: {
        id: 1,
        name: 'Dan'
      }
    }
  }
}

我想到的每一种方法都需要一些复杂的对象操作,这让我觉得我没有走上正确的轨道,因为普通人应该让我的生活更轻松 .

我找不到任何以这种方式使用normalizr树的人在线的例子 . The official example没有添加和删除所以它也没有任何帮助 .

有人能让我知道如何以正确的方式添加/删除normalizr树吗?

4 回答

  • 3

    以下内容直接来自redux / normalizr creator here的帖子:

    所以你的州看起来像:

    {
      entities: {
        plans: {
          1: {title: 'A', exercises: [1, 2, 3]},
          2: {title: 'B', exercises: [5, 1, 2]}
         },
        exercises: {
          1: {title: 'exe1'},
          2: {title: 'exe2'},
          3: {title: 'exe3'}
        }
      },
      currentPlans: [1, 2]
    }
    

    你的减速器可能看起来像

    import merge from 'lodash/object/merge';
    
    const exercises = (state = {}, action) => {
      switch (action.type) {
      case 'CREATE_EXERCISE':
        return {
          ...state,
          [action.id]: {
            ...action.exercise
          }
        };
      case 'UPDATE_EXERCISE':
        return {
          ...state,
          [action.id]: {
            ...state[action.id],
            ...action.exercise
          }
        };
      default:
        if (action.entities && action.entities.exercises) {
          return merge({}, state, action.entities.exercises);
        }
        return state;
      }
    }
    
    const plans = (state = {}, action) => {
      switch (action.type) {
      case 'CREATE_PLAN':
        return {
          ...state,
          [action.id]: {
            ...action.plan
          }
        };
      case 'UPDATE_PLAN':
        return {
          ...state,
          [action.id]: {
            ...state[action.id],
            ...action.plan
          }
        };
      default:
        if (action.entities && action.entities.plans) {
          return merge({}, state, action.entities.plans);
        }
        return state;
      }
    }
    
    const entities = combineReducers({
      plans,
      exercises
    });
    
    const currentPlans = (state = [], action) {
      switch (action.type) {
      case 'CREATE_PLAN':
        return [...state, action.id];
      default:
        return state;
      }
    }
    
    const reducer = combineReducers({
      entities,
      currentPlans
    });
    

    那么这里发生了什么?首先,请注意状态是规范化的 . 我们从未在其他实体内部拥有实体 . 相反,它们通过ID互相引用 . 因此,每当某个对象发生变化时,只有一个地方需要更新 .

    其次,注意我们如何通过在计划缩减器中添加适当的实体并将其ID添加到currentPlans reducer来对CREATE_PLAN做出反应 . 这个很重要 . 在更复杂的应用中,您可能拥有关系,例如计划reducer可以通过在计划内的数组中附加新ID来以相同的方式处理ADD_EXERCISE_TO_PLAN . 但是如果练习本身已经更新,那么计划缩减器就不需要知道,因为ID没有改变 .

    第三,注意实体减少者(计划和练习)有特殊条款,注意行动 . 这是因为我们有一个“已知真相”的服务器响应,我们想要更新我们要反映的所有实体 . 要在分派操作之前以这种方式准备数据,可以使用normalizr . 您可以在Redux仓库中的“真实世界”示例中看到它 .

    最后,请注意实体减速器是如何相似的 . 您可能想要编写一个函数来生成它们 . 这超出了我的答案范围 - 有时你需要更多的灵活性,有时你想要更少的样板 . 您可以查看“真实世界”示例缩减器中的分页代码,以获取生成类似缩减器的示例 .

    哦,我用{... a,... b}语法 . 它在Babel阶段2中作为ES7提议启用 . 它被称为“对象扩展运算符”,相当于编写Object.assign({},a,b) .

    至于库,你可以使用Lodash(注意不要改变,例如merge({},a,b}是正确的但是merge(a,b)不是),updeep,react-addons-update或其他东西 . 但是,如果您发现自己需要进行深度更新,则可能意味着您的状态树不够扁平,并且您没有充分利用功能组合 . 即使是您的第一个示例:

    case 'UPDATE_PLAN':
      return {
        ...state,
        plans: [
          ...state.plans.slice(0, action.idx),
          Object.assign({}, state.plans[action.idx], action.plan),
          ...state.plans.slice(action.idx + 1)
        ]
      };
    

    可写成

    const plan = (state = {}, action) => {
      switch (action.type) {
      case 'UPDATE_PLAN':
        return Object.assign({}, state, action.plan);
      default:
        return state;
      }
    }
    
    const plans = (state = [], action) => {
      if (typeof action.idx === 'undefined') {
        return state;
      }
      return [
        ...state.slice(0, action.idx),
        plan(state[action.idx], action),
        ...state.slice(action.idx + 1)
      ];
    };
    
    // somewhere
    case 'UPDATE_PLAN':
      return {
        ...state,
        plans: plans(state.plans, action)
      };
    
  • 0

    大多数时候我使用normalizr来获取从API获得的数据,因为我对(通常)深层嵌套数据结构没有任何控制权 . 让我们区分实体和结果及其用法 .

    Entities

    所有纯数据在规范化后都在实体对象中(在您的情况下为 articlesusers ) . 我建议对所有实体使用reducer或为每个实体类型使用reducer . 实体减速机应负责 keep your (server) data in sync and to have a single source of truth.

    const initialState = {
      articleEntities: {},
      userEntities: {},
    };
    

    Result

    结果仅是对您的实体的引用 . 想象一下以下场景:(1)您从推荐 articles 的API中获取 ids: ['1', '2'] . 您可以在文章实体reducer中保存实体 . (2)现在,您将使用 id: 'X' 获取特定作者撰写的所有文章 . 再次,您同步文章实体reducer中的文章 . 文章实体缩减器是所有文章数据的唯一真实来源 - 就是这样 . 现在你想要另一个地方来区分文章((1)推荐文章和(2)作者X的文章) . 您可以轻松地将这些保存在另一个特定用途的减速器中 . reducer的状态可能如下所示:

    const state = {
      recommended: ['1', '2' ],
      articlesByAuthor: {
        X: ['2'],
      },
    };
    

    现在您可以很容易地看到作者X的文章也是推荐的文章 . 但是你在文章实体缩减器中只保留了一个单一的事实来源 .

    在您的组件中,您可以简单地映射推荐的实体/ articlesByAuthor来呈现实体 .

    免责声明:我可以推荐一篇我写的博客文章,其中展示了如何真实世界的应用程序使用normalizr来防止状态管理中的问题:Redux Normalizr: Improve your State Management

  • 28

    我已经实现了通用减速器的小偏差,可以在互联网上找到 . 它能够从缓存中删除项目 . 您所要做的就是确保在每次删除时都发送一个包含已删除字段的操作:

    export default (state = entities, action) => {
        if (action.response && action.response.entities)
            state = merge(state, action.response.entities)
    
        if (action.deleted) {
            state = {...state}
    
            Object.keys(action.deleted).forEach(entity => {
                let deleted = action.deleted[entity]
    
                state[entity] = Object.keys(state[entity]).filter(key => !deleted.includes(key))
                    .reduce((p, id) => ({...p, [id]: state[entity][id]}), {})
            })
        }
    
        return state
    }
    

    动作代码中的用法示例:

    await AlarmApi.remove(alarmId)
    
    dispatch({
        type: 'ALARM_DELETED',
        alarmId,
        deleted: {alarms: [alarmId]},
    })
    
  • 2

    在reducer中,保留未规范化数据的副本 . 这样,您可以执行以下操作(将新对象添加到状态中的数组时):

    case ACTION:
      return {
        unNormalizedData: [...state.unNormalizedData, action.data],
        normalizedData: normalize([...state.unNormalizedData, action.data], normalizrSchema),
      }
    

    如果您不想在商店中保留未规范化的数据,也可以使用denormalize

相关问题