Изменение значения объекта массива переменных состояния

У меня есть массив объектов в качестве переменной состояния. Я пытаюсь написать функцию, которая изменяет поле в одном из объектов.

interface IItem{
 id: string,
 selected: boolean
}

const [array, setArray] = useState<IItem[]>([])

const toggleItem = (id : string) => {
  const item = array.find(i => i.id === id)
  if (item) {
     const copy = {...item, selected: !!item.selected}
     setArray(array.map(i => i.id === id ? copy : i))
  }
} 

Код работает, но выглядит слишком сложным для этого. Есть ли способ упростить? (Обратите внимание, что порядок элементов в массиве имеет значение).

Вы можете перейти на использование Immer, см. эту среднюю статью: medium.com/@agarwalsahil97/…

user3791775 24.06.2024 18:51

«это выглядит слишком сложным для этого» React полагается на дешевую семантику сравнения (т. е. равенство ссылок) для эффективного различия изменений в состоянии, в противном случае ему пришлось бы выполнять рекурсивную проверку для каждого объекта состояния (обратите внимание, что для этого потребуется много выделений и операций), что было бы слишком медленно. Но это требование неизменности плохо сочетается с семантикой Javascript. Ваши единственные реальные варианты — написать код так, как вы написали, использовать библиотеку неизменяемых структур данных или не использовать React.

Jared Smith 24.06.2024 19:05
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
2
53
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Вы можете упростить функцию следующим образом: (same thing, just reduced lines!)

const toggleItem = (id: string) => {
   setArray(array.map(i => (i.id === id) ? {...i, selected: !i.selected} : i));
};
  • Если идентификатор совпадает, мы создаем новый объект с теми же свойствами, что и текущий элемент (...i), но мы переключаем выбранное поле с помощью !Я выбрал.

В основном это просто сжимает его. Единственное упрощение, которое вы сделали, помимо удаления пробелов или локальных значений, заключается в том, что теперь вы безоговорочно повторяете массив и устанавливаете состояние (которое запускает повторную отрисовку), которое может не иметь приемлемых характеристик производительности или соответствовать потребностям OP. Я не говорю, что это плохой ответ, но если вы собираетесь это сделать, то хотя бы оставьте описание изменений.

Jared Smith 24.06.2024 19:19
Ответ принят как подходящий

Если вы хотите упростить изменение состояния, я бы рекомендовал ознакомиться с пакетом Immer. С помощью Immer вы получаете записываемый черновик состояния, который можно изменять. Код может быть очень длинным, но если вы работаете с еще более глубоко вложенными объектами, это очень полезно. Я также думаю, что в вашем случае это очень помогает с читабельностью.

import { useState } from "react";
import { produce } from "immer";

interface IItem {
  id: string;
  selected: boolean;
}

const [array, setArray] = useState<IItem[]>([]);

const toggleItem = (id: string) => {
  setArray(
    produce((draft) => {
      const item = draft.find((el) => el.id === id);
      item.selected = !item.selected;
    }),
  );
};

РЕДАКТИРОВАТЬ

Если вы не хотите вводить пакет для локального состояния, я не думаю, что есть очевидный способ изменить ваш код, чтобы сделать его менее сложным. По крайней мере, вот альтернатива, которую, я бы сказал, немного легче читать.

interface IItem {
  id: string;
  selected: boolean;
}

const [array, setArray] = useState<IItem[]>([]);

const toggleItem = (id: string) => {
  setArray((previous) =>
    previous.map((item) => {
      if (item.id === id) {
        return {
          ...item,
          selected: true,
        };
      }
      return item;
    }),
  );
};

Спасибо — это действительно было бы хорошим решением, но я не хочу вводить immer для местного государственного управления.

Vlad Z 24.06.2024 22:09

Я понимаю, я добавил альтернативу, но это не сильно отличается от вашего текущего кода. Возможно, небольшое улучшение читабельности.

Felix Eklöf 24.06.2024 22:21

Скройте это в специальном хуке:

interface IItem {
  id: string,
  selected: boolean
}

function useSelect<T extends IItem>({ initialItems = [] }) {
  const [items, setItems] = useState<T[]>(initialItems);

  const toggleItem = (id: string) => {
    const item = items.find(i => i.id === id);
    if (item) {
      const copy = { ...item, selected: !!item.selected }
      setItems(items.map(i => i.id === id ? copy : i))
    }

    return { items, toggleItem };
  }
}

и используйте его как:

function MyView() {
  const { items, toggleItem } = useSelect({ initialItems: [...] });

  return (
    <>
      {items.map(({ id, selected }) => (
        <div
          key = {id}
          onClick = {() => toggleItem(id)}>
          {id} {selected ? '(selected)' : ''}
        </div>
      ))}
    </>
  )
}

Другие вопросы по теме