Проблема мутаций с фильтрацией объектов в Javascript - PullRequest
0 голосов
/ 15 ноября 2018

Я начал с реакции несколько месяцев назад, и мне показалось, что до сих пор я достаточно хорошо понимал мутации.

У меня есть функция, которая принимает объект, выбранный itemId и parentId (необязательно).Данные могут иметь вложенные элементы до 1 уровня.Что мне нужно сделать, это изменить значение ключа selected, если itemId совпадает.Если itemId находится внутри родителя, тогда мне нужно установить selected внутри этого объекта.

Другой вариант использования, если itemId является идентификатором родителя.В этом случае мне нужно также установить значение 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);
});

Спасибо за вашу помощь!

1 Ответ

0 голосов
/ 15 ноября 2018

const originalData = data.map(item => item) создает только поверхностную копию массива data. И, следовательно, любой элемент object, который вы изменяете в массиве originalData, приведет к мутации массива data. И, следовательно, вы должны глубоко скопировать массив data. Для удобства используйте библиотеку типа Lodash .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...