How deep do I need to clone my state when only updating parts of the state?

时间:2017-06-20 12:36:48

标签: reactjs redux react-redux

Until recently, I've always just used lodash's cloneDeep to make a copy of my state, then change values and return the cloned state. For example like this:

This would be my initial state:

{
    "id": 1213,
    "title": "Some title...",
    "pages": {
        "page1": {
            "id": 459,
            "title": "Some Page title...",
            "fields": {
                "field_1": {
                    "title": "My field",
                    "type": "text",
                    "value": "my text value..."
                },
                "field_2": {
                    "title": "My field 2",
                    "type": "text",
                    "value": "my text two value..."
                },
                "field_3": {
                    "title": "My field 3",
                    "type": "text",
                    "value": "my text value..."
                }
            }
        }
    }
}

Now, I want to update the value of field_2.

My redux reducer would look like this:

import cloneDeep from 'lodash/fp/cloneDeep';

export default function reducer(state, action) {
  const {type, payload} = action;
  switch (type) {
    case 'UPDATE_FIELD_VALUE': {
      const { pageIdent, fieldIdent, newValue } = payload;
      // This is what I'm doing right now....
      const newState = cloneDeep(state);
      newState.pages[pageIdent]fields[fieldIdent]value = newValue;
      return newState;
      // Instead could I do this?
      return {
        ...state,
        state.pages[pageIdent]fields[fieldIdent]value = newValue;
      }
    }
  }
}

So, I've read that I don't always have to do deep clone...but in other places I've read that you cannot return the same object, you have to return new objects at all times. So what is the right way to do this?

3 个答案:

答案 0 :(得分:1)

是的,不要那样做。引用the Redux FAQ on whether you should deep-clone state

  

不可更新的状态通常意味着制作浅拷贝,而不是深拷贝。浅拷贝比深拷贝快得多,因为必须复制更少的对象和字段,并且它有效地归结为移动一些指针。

     

但是,您需要为受影响的每个嵌套级别创建复制和更新的对象。虽然这不应该特别昂贵,但这是另一个很好的理由,为什么你应该尽可能保持你的国家正常化和浅薄。

     
    

Common Redux误解:你需要深度克隆状态。现实:如果内部的某些内容没有变化,请保持其引用相同!

  

所以,你不想要"深度克隆",你需要"嵌套的浅克隆"。

深度克隆在两个方面对性能有害:克隆所有内容需要做更多的工作,新对象引用会导致实际上没有的数据的UI更新值的变化(但是新的引用使得UI 认为发生了变化)。

您应该阅读Redux docs page on "Immutable Update Patterns"。这是该页面的嵌套状态更新示例:

function updateVeryNestedField(state, action) {
    return {
        ....state,
        first : {
            ...state.first,
            second : {
                ...state.first.second,
                [action.someId] : {
                    ...state.first.second[action.someId],
                    fourth : action.someValue
                }
            }
        }
    }
}

如果你觉得这太繁琐或痛苦,你应该改变你的状态结构,以便它更平坦,或者你可以使用那里的许多immutable update utility libraries中的一个来处理更新过程适合你。

答案 1 :(得分:0)

你真的不应该总是克隆状态对象。如果你可以确保:Redx会发光:

  1. 状态的部分,根据提供给reducer的操作,更改是应更改的部分。因此,如果您要更新field_1,则field_2不得更改。
  2. 如果有一部分状态应该更改,则始终会发生变化。因此,如果您的操作更新field_2,则field_2对象引用应该更改。
  3. 如果您的州允许“深度”更新,这会容易得多。状态规范化是redux应用程序中使用的更好的“模式”之一,并在文档中进行了描述。

    例如,让我们重新调整你的状态(假设顶级是一个book对象,并且字段ID是全局唯一的):

    初始状态

    "books" : {
        "1213": {
            "id": 1213,
            "title": "Some title...",
            "pages: [..., "page1", ...],
        }
    },
    "pages": {
        "page1": {
            "id": 459,
            "title": "Some Page title...",
            "fields": [..., "field_1", "field_2", "field_3", ...],
        }
    },
    "fields": {
        "field_1": {
            "title": "My field",
            "type": "text",
            "value": "my text value..."
        },
        "field_2": {
            "title": "My field",
            "type": "text",
            "value": "my text value..."
        },
        "field_3": {
            "title": "My field",
            "type": "text",
            "value": "my text value..."
        }
    }
    

    请注意,每个“图书”实体都有一个页面ID列表,而不是嵌套在其中的完整对象。同样,每个页面都有一个字段ID列表,而不是实际字段。这样,所有携带数据的实体都存储在状态的“顶级”,并且可以独立更新而不会触及整个状态。

    通过展平您的州结构,您可以创建仅与您所在州的一小部分相关的“子减速器”。在上面的例子中,我有:

    import { combineReducers } from 'redux';
    
    // This reducer handles all actions that affect book entities
    const books = (state = {}, action = {}) => state;
    
    // This reducer handles all actions that affect page entities
    const pages = (state = {}, action = {}) => state;
    
    // This reducer handles all actions that affect field entities. 
    // For your problem, this would look like:
    const fields = (state = initialFields, action = {}) => {
        switch (action.type) {
            case 'UPDATE_FIELD_VALUE':
                return { 
                    ...state, 
                    [fieldIdent]: {
                        ...state[fieldIdent],
                        value: newValue,
                    }
                }
          default:
              return state;
        }
    }
    
    // This results in the state structure above
    const reducer = combineReducers({
        books,
        pages,
        fields,
    });
    

    在上面的代码中,更改字段对页面或书籍实体没有影响,这可以防止不必要的重新渲染。话虽这么说,更改field_2值将肯定导致新的field_2对象,并根据需要重新渲染。

    有些库可以帮助您从JSON API响应数据中构建您的状态。这是一个相当不错的选择:https://github.com/paularmstrong/normalizr

    希望这有帮助!

答案 2 :(得分:-1)

我认为facebook的不可变js解决了你所有的问题。阅读文档here