首页 > 解决方案 > Javascript中对象过滤的变异问题

问题描述

几个月前我从 react 开始,我认为直到现在我对突变的理解已经足够好了。

我有一个接受对象、选定 itemId 和 parentId(可选)的函数。数据可以具有最多 1 级的嵌套元素。selected如果 itemId 匹配,我需要做的是更改键的值。如果 itemId 在父对象内部,那么我需要selected在该对象内部进行设置。

另一个用例是,如果 itemId 是父项的 id。在这种情况下,我还需要设置所有子项的 selected 值。这就是问题所在。当我尝试设置孩子的selected值时,该函数正在改变我发送的数据,因此返回错误的结果。

这是有问题的功能:

function returnSelectedItems(data, itemId, parentId) {
  // created this object trying to rule out mutation problem in the tests. @see tests file.
  const originalData = data.map(item => item);
  if (parentId) {
    // if parent id is present, a child element has been selected, then directly go to the element
    // go through all the items
    return originalData.map(parentItem => {
      // see if the id matches the parent item
      if (parentItem.id === parentId) {
        // if yes, check if it contains any children
        if (parentItem.children && parentItem.children.length > 0) {
          // iterate through their children and assign the resulting array as the children for the parent
          parentItem.children = parentItem.children.map(childItem => {
            // find the child with the id selected and change its value
            if (childItem.id === itemId) {
              childItem.selected = !childItem.selected;
            }
            return childItem;
          });
        }
      }
      return parentItem;
    });
  } else {
    return originalData.map(item => {
      if (item.id === itemId) {
        // set the item to the opposite it was before
        item.selected = !item.selected;

        // if it has children, we have to set the values for all of them.
        if (item.children && item.children.length > 0) {
          item.children = item.children.map(childItem => {
            childItem.selected = item.selected;
            return childItem;
          });
        }
      }
      return item;
    });
  }
}

以下是我创建的测试,以测试它是否按预期工作:

test('WITHOUT children test for selectedItems function', () => {
  const data = [
    {
      id: 1,
      name: 'test',
      selected: false
    },
    {
      id: 2,
      name: 'test',
      selected: false
    },
    {
      id: 3,
      name: 'test',
      selected: false
    },
    {
      id: 4,
      name: 'test',
      selected: false
    },
    {
      id: 5,
      name: 'test',
      selected: false
    }
  ];
  expect(returnSelectedItems(data, 4)).toEqual(
    data.map(item => {
      if (item.id === 4) {
        item.selected = true;
      }
      return item;
    })
  );
  expect(returnSelectedItems(data, 5)).toEqual(
    data.map(item => {
      if (item.id === 5) {
        item.selected = true;
      }
      return item;
    })
  );
  expect(
    returnSelectedItems(
      data.map(item => {
        if (item.id === 4) {
          item.selected = true;
        }
        return item;
      }, 4)
    )
  ).toEqual(data);
  expect(
    returnSelectedItems(
      data.map(item => {
        if (item.id === 5) {
          item.selected = true;
        }
        return item;
      }, 5)
    )
  ).toEqual(data);
});

test('WITH children test for selectedItems function', () => {
  const data = [
    {
      id: 1,
      name: 'test',
      selected: false,
      children: [{id: 1, name: 'test', selected: false}, {id: 2, name: 'test', selected: false}, {id: 3, name: 'test', selected: false}]
    },
    {
      id: 2,
      name: 'test',
      selected: false,
      children: [{id: 1, name: 'test', selected: false}, {id: 2, name: 'test', selected: false}, {id: 3, name: 'test', selected: false}]
    },
    {
      id: 3,
      name: 'test',
      selected: false,
      children: [{id: 1, name: 'test', selected: false}, {id: 2, name: 'test', selected: false}, {id: 3, name: 'test', selected: false}]
    },
    {
      id: 4,
      name: 'test',
      selected: false,
      children: [{id: 1, name: 'test', selected: false}, {id: 2, name: 'test', selected: false}, {id: 3, name: 'test', selected: false}]
    },
    {
      id: 5,
      name: 'test',
      selected: false,
      children: [{id: 1, name: 'test', selected: false}, {id: 2, name: 'test', selected: false}, {id: 3, name: 'test', selected: false}]
    }
  ];

  const backupData = [
    {
      id: 1,
      name: 'test',
      selected: false,
      children: [{id: 1, name: 'test', selected: false}, {id: 2, name: 'test', selected: false}, {id: 3, name: 'test', selected: false}]
    },
    {
      id: 2,
      name: 'test',
      selected: false,
      children: [{id: 1, name: 'test', selected: false}, {id: 2, name: 'test', selected: false}, {id: 3, name: 'test', selected: false}]
    },
    {
      id: 3,
      name: 'test',
      selected: false,
      children: [{id: 1, name: 'test', selected: false}, {id: 2, name: 'test', selected: false}, {id: 3, name: 'test', selected: false}]
    },
    {
      id: 4,
      name: 'test',
      selected: false,
      children: [{id: 1, name: 'test', selected: false}, {id: 2, name: 'test', selected: false}, {id: 3, name: 'test', selected: false}]
    },
    {
      id: 5,
      name: 'test',
      selected: false,
      children: [{id: 1, name: 'test', selected: false}, {id: 2, name: 'test', selected: false}, {id: 3, name: 'test', selected: false}]
    }
  ];

  // this console log is correct
  console.log(data.filter(item => item.id === 4)[0]);
  const dataBack = returnSelectedItems(data, 4);
  // this console is also correct
  console.log(dataBack.filter(item => item.id === 4)[0]);
  // but here the value of the data is changed
  console.log(data.filter(item => item.id === 4)[0]);

  // this passes, but it shouldn't
  expect(returnSelectedItems(data, 4)).toEqual(
    data.map(item => {
      if (item.id === 4) {
        item.selected = true;
      }
      return item;
    })
  );

  // this should pass but it does not
  expect(backupData).toEqual(data);
});

谢谢你的帮助!

标签: javascriptreactjsimmutability

解决方案


唯一创建数组的const originalData = data.map(item => item)浅表副本。data因此object,您在数组中修改的任何项目originalData都会导致数组的突变data。因此,您必须对data数组进行深度复制。为方便起见,请使用Lodash 之类的库。


推荐阅读