即使不变异对象和数组,也无法在reducer中更改嵌套对象 [英] can't change nested object in a reducer even when not mutating the objects and arrays

查看:57
本文介绍了即使不变异对象和数组,也无法在reducer中更改嵌套对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试更新化简器中的状态,我知道我不应该对对象或嵌套对象进行突变,因此我对数组使用数组或对象进行对象散布.但似乎我无法真正更改深层嵌套的值.

I'm trying to update a state inside a reducer, i know i shouldn't mutate the object or nested objects so I'm using map for arrays or object spread for objects. but it seems i can't really change a value that is deeply nested.

除了我无法更改状态外,我真的不喜欢代码的外观,特别是只更改一个属性所需执行的循环数.我觉得有一种更好,更易读,更高性能的方法.

Beside the fact that i can't change the state, i really dislike how the code looks and especially the number of loops i need to do to just change one property. I feel like there is a better, readable and more performant way of doing this.

这是状态:

const items = [{
  name: 'item 1',
  id: 'item1',
  tags: [{
    id: 'tag1',
    name: 'tag 1'
  }, {
    id: 'tag2',
    name: 'tag 2'
  }]
}, {
  name: 'item 2',
  id: 'item2',
  tags: [{
    id: 'tag1',
    name: 'tag 1'
  }, {
    id: 'tag4',
    name: 'tag 4'
  }]
}];

这是我要分派的动作:

const action = {
  type: 'CHANGE_TAG_NAME',
  payload: {
    itemId: 'item2',
    tagId: 'tag4',
    newTagName: 'tag 44444'
  }
};

这是减速器:

const itemsReducer = (state = [], action) => {
  switch (action.type) {
    case 'CHANGE_TAG_NAME':
      {
        const itemIndex = state.findIndex(item => item.id === action.payload.itemId);
        const tagIndex = state[itemIndex].tags.findIndex(t => t.id === action.payload.tagId);
        const nextTag = {
          ...state[itemIndex].tags[tagIndex],
            name: action.payload.newTagName
        };
        const nextTags = [
          ...state[itemIndex].tags.slice(0, tagIndex),
          nextTag,
          ...state[itemIndex].tags.slice(tagIndex + 1, ),
        ];
        const nextItem = {
          ...state[itemIndex],
            tags: nextTags
        };

        const nextState = [
          ...state.slice(0, itemIndex),
          nextItem,
          ...state.slice(itemIndex + 1)
        ];
      }
    default:
      return state;
  }
};

推荐答案

您的reducer应该可以正常工作,只是忘记了在case块中返回nextState.

Your reducer should work just fine, you just forgot to return nextState in your case block.

对于较少的迭代,我建议采用这种模式:
映射项目,如果当前项目的ID与有效负载中的itemId不同,则按原样返回.
如果项目的ID相同,则返回一个新对象,然后映射到标签上,并执行与该项目相同的条件.
如果标签的ID与有效负载中的tagId不同,则按原样返回它,如果相同,则返回一个新对象.

As for less iterations i suggests this pattern:
map over the items, if the current item's id is different from the itemId you have in the payload then return it as is.
If the item's id is the same then return a new object and then map over the tags, doing the same condition like you did with the item.
If the tag's id isn't the same as the tagId in the payload return it as is, if it does the same return a new object.

这是一个正在运行的示例:

Here is a running example:

const items = [{
  name: 'item 1',
  id: 'item1',
  tags: [{
    id: 'tag1',
    name: 'tag 1'
  }, {
    id: 'tag2',
    name: 'tag 2'
  }]
}, {
  name: 'item 2',
  id: 'item2',
  tags: [{
    id: 'tag1',
    name: 'tag 1'
  }, {
    id: 'tag4',
    name: 'tag 4'
  }]
}];

const action = {
  type: 'CHANGE_TAG_NAME',
  payload: {
    itemId: 'item2',
    tagId: 'tag4',
    newTagName: 'tag 44444'
  }
};

const itemsReducer = (state = [], action) => {
  switch (action.type) {
    case 'CHANGE_TAG_NAME':
      {
        const {
          payload: {
            itemId,
            tagId,
            newTagName
          }
        } = action;
        const nextState = state.map(item => {
          if (item.id !== itemId) return item;
          return {
            ...item,
            tags: item.tags.map(tag => {
              if (tag.id !== tagId) return tag;
              return {
                ...tag,
                name: newTagName
              }
            })
          }
        });
        return nextState;
      }
    default:
      return state;
  }
};

console.log(itemsReducer(items, action));

对于更具可读性的代码,我建议使用更多的reducer.
我使用的经验法则是为每个实体创建一个化简器:
itemsReducer,
itemReducer,
标签减速器,
tagReducer.

As for a more readable code, i suggest to use more reducers.
A thumb of rule i use is to create a reducer per entity:
itemsReducer,
itemReducer,
tagsReducer,
tagReducer.

这样,每个reducer将负责其自己的数据.

This way each reducer will be responsible for its own data.

这是一个正在运行的示例:

Here is a running example:

const items = [{
  name: 'item 1',
  id: 'item1',
  tags: [{
    id: 'tag1',
    name: 'tag 1'
  }, {
    id: 'tag2',
    name: 'tag 2'
  }]
}, {
  name: 'item 2',
  id: 'item2',
  tags: [{
    id: 'tag1',
    name: 'tag 1'
  }, {
    id: 'tag4',
    name: 'tag 4'
  }]
}];

const action = {
  type: 'CHANGE_TAG_NAME',
  payload: {
    itemId: 'item2',
    tagId: 'tag4',
    newTagName: 'tag 44444'
  }
};

const tagReducer = (state = {}, action) => {
  switch (action.type) {
    case 'CHANGE_TAG_NAME':
      {
        const {
          payload: {
            newTagName
          }
        } = action;
        const nextState = {
          ...state,
          name: newTagName
        }
        return nextState;
      }
    default:
      return state;
  }
}

const tagsReducer = (state = [], action) => {
  switch (action.type) {
    case 'CHANGE_TAG_NAME':
      {
        const {
          payload: {
            tagId
          }
        } = action;
        const nextState = state.map(tag => {
          if (tag.id !== tagId) return tag;
          return tagReducer(tag, action);
        });
        return nextState;
      }
    default:
      return state;
  }
}


const itemReducer = (state = {}, action) => {
  switch (action.type) {
    case 'CHANGE_TAG_NAME':
      {
        const nextState = {
          ...state,
          tags: tagsReducer(state.tags, action)
        }
        return nextState;
      }
    default:
      return state;
  }
}

const itemsReducer = (state = [], action) => {
  switch (action.type) {
    case 'CHANGE_TAG_NAME':
      {
        const {
          payload: {
            itemId
          }
        } = action;
        const nextState = state.map(item => {
          if (item.id !== itemId) return item;
          return itemReducer(item, action)
        });
        return nextState;
      }
    default:
      return state;
  }
};

console.log(itemsReducer(items, action));

这种模式通常被称为化简器组成,您不必将它们全部包含在根化简器中,只需将它们用作外部纯函数即可为其他化简器计算状态的相关部分.

This pattern is often called reducer composition, and you don't have to include all of them in your root reducer, just use them as external pure functions that will calculate the relevant portion of the state for your other reducers.

这篇关于即使不变异对象和数组,也无法在reducer中更改嵌套对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆