2016-01-22 38 views
29

展望从README例子:如何添加/删除使用normalizr生成的redux商店?

鉴于“坏”的结构:

[{ 
    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是应该让我的生活更轻松。

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

有人能让我知道如何添加/从标准化树中删除正确的方式吗?

回答

25

从后通过了Redux/normalizr创作者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做出反应。这个很重要。在更复杂的应用程序中,您可能有关系,例如计划缩减器可以通过向计划内的数组附加新ID来以相同的方式处理ADD_EXERCISE_TO_PLAN。但是如果练习本身已经更新,那么就不需要计划减速器知道这一点,因为ID没有改变。

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

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

哦,我用{... a,... b}语法。它作为ES7提案在Babel阶段2中启用。它被称为“对象扩展运算符”,相当于写入Object.assign({},a,b)。对于库,你可以使用Lodash(注意不要改变,例如merge({},a,b}是正确的,但合并(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) 
    ] 
    }; 

CAN写为

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

谢谢@ AR7这个精彩的解释。我有一个问题:为什么我们需要将currentPlans数组保持在状态并保持它的更新(当然,如果你拥有状态,当然最少更新它,但是它在别处使用了什么) ?在这个州制定计划的目标还不够吗?它在实践中用于什么?我注意到Redux文档以及normalizr文档提到了这些数组。 – Cedric

+0

@Cedric从我的角度来看,它用于保持对象的顺序。哈希映射没有顺序,所以如果只保留计划对象,每次刷新页面顺序都可能完全不同。你也不能遍历任何MVC框架中的对象,所以你需要在响应中做一些像Object.keys(plans).map()这样的事情,而不是仅仅使用当前的计划数组。 – m0meni

+0

@Cedric也没有写这个哈哈。它本身就是redux/normalizr的创造者! – m0meni

3

大部分时间我使用normalizr来获取API中的数据,因为我无法控制(通常)深层嵌套的数据结构。让我们区分实体和结果及其用法。它已经常态化后,(在你的情况articlesusers

实体

所有的纯数据是在实体对象。我建议要么为所有实体使用reducer,要么为每个实体类型使用reducer。实体reducer(s)应该负责保持您的(服务器)数据同步,并有一个单一的来源。

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

结果

的结果是你的实体仅引用。设想以下情况:(1)您从API推荐articles获取ids: ['1', '2']。您将实体保存在您的物品实体缩减器中。 (2)现在,您可以使用id: 'X'获取特定作者撰写的所有文章。再次同步物品实体缩减器中的文章。 物品实体缩减器是您的所有商品数据的唯一真实来源 - 多数民众赞成它。现在你想有另一个地方来区分这些文章((1)推荐文章和(2)作者X的文章)。您可以轻松地将这些保存在另一个使用案例中的特定缩减器中该减速的状态可能是这样的:

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

现在你可以很容易地看到,该文章的作者X是推荐的文章,以及。但是,在物品实体缩减器中只保留一个真实的来源。

在您的组件中,您可以简单地映射实体+ recommended/articlesByAuthor来呈现实体。

声明:我可以推荐一个博客帖子我写的,这显示了一个现实世界的应用如何使用normalizr防止状态管理问题:Redux Normalizr: Improve your State Management

2

我已经实现了一个通用减速器可以是一个小的偏差通过互联网找到。它能够从缓存中删除项目。所有你需要做的就是确保每个删除您已删除字段发送一个动作:在动作代码

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]}, 
}) 
0

在你减速,保持联合国的副本标准化数据。这样一来,你可以做这样的事情(添加一个新的对象在状态数组时):

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

如果你不想保持未归一化数据,你的店,你也可以使用denormalize

+0

这里的主要红旗。首先,你应该避免在商店中重复数据。它要求麻烦,是一种代码味道。此外,减速器应尽可能精简,并且在每个周期内调用标准化不是推荐用法。 – sweeds

+0

当您使用复杂模式进行规范化时,您会如何建议更新/删除?例如,idAttribute是一个函数,并且使用了过程和合并策略?这种方法非常简单直接,从来没有给我带来任何性能问题。 –

+0

如果您对规范化数据进行了修改,现在非规范化的重复数据(“unNormalizedData”)已过期。 – sweeds