Скопируйте массив объектов для использования в качестве состояния — React.js

#javascript #reactjs

Вопрос:

Мне трудно понять, почему, когда внутри вектора есть объекты, он не создает новых ссылок на них при копировании вектора.

Но вот в чем проблема.

 const USERS_TYPE = [
    {name:"User",icon:faTruck,
    inputs:[
        {label:"Name",required:true,width:"200px",value:"", error:false},
        {label:"ID",required:true,width:"150px",value:"", error:false},
        {label:"Email",required:false,width:"150px",value:"", error:false},
    ]}]
 

Я попытался передать этот вектор в состояние двумя способами.

 const [users,setUsers] = useState(USERS_TYPE.map(item=>({...item})))
const [users,setUsers] = useState([...USERS_TYPE])
 

И в обеих ситуациях изменение пользователя с помощью setUser приведет к изменению типа ПОЛЬЗОВАТЕЛЯ.

Один из способов, которым я меняюсь.

     const changes = [...users] 
    const err = validation(changes[selected-1].inputs)
    err.map((index)=>{
        changes[selected-1].inputs[index].error = true
    })
    setUsers(changes)
 

Какие решения я мог бы придумать, перейти от вектора к объекту, другой механизм копирования.
Эта копия не имеет особого смысла, поскольку внутренние ссылки на объекты остаются нетронутыми.

Изменить: Еще одна важная деталь заключается в том, что тип пользователя находится вне компонента функции.

Ответ №1:

это не создает новых ссылок на них при копировании вектора

Потому что JS работает не так. Он не делает глубокие клонирования автоматически.

 const user1 = {id:1}
const user2 = {id:2}
const users = [user1,user2]; 
const newUsers = [...users]; // this clones users, BUT NOT the objects it contains
console.log(newUsers === users); // false, it's a new array
console.log(newUsers[0] === users[0]) // true, it's the same reference
 

В конечном счете, вы просто мутируете состояние. Первое и самое золотое правило реакции: не изменяйте состояние.

Это строка, вызывающая ошибку:

     err.map((index)=>{
        // you are mutating an object by doing this
        // yes, `changes` is a new array, but you are still mutating the object that is part of state that is nested inside of that array
        changes[selected-1].inputs[index].error = true
    })
 

Может быть, это сработает:

     const idx = selected-1;
    const err = validation(users[idx].inputs)
    setUsers(users => users.map((user,i) => {
      if(i !== idx) return user; // you aren't interested in this user, leave it unmodified

      
      // you need to change the inputs for this user
      // first, shallow clone the object using the spread operator
      return {
        ...user,
        // now inputs must be a new reference as well, so clone it using map
        inputs: user.inputs.map((input,index) => ({
          // careful not to mutate the input object, clone it using spread
          ...input,
          // and set the error property on the cloned object
          error: !!err[index]
        }))
      }
    }))
 

ПРАВКА: Извините за все правки кода, у меня была куча синтаксических ошибок. Конечная точка, которую я пытался донести, оставалась неизменной.

ПРАВКА №2:

Еще одна важная деталь заключается в том, что тип пользователя находится вне компонента функции.

В данном случае это на самом деле не имеет значения, поскольку служит вашим начальным состоянием. Каждый раз, когда вы обновляете состояние, вам нужно делать это неизменно (как я показал вам выше), чтобы не изменять этот глобальный объект. Если вы действительно измените его, вы увидите, что размонтирование компонента и повторная установка компонента приведут к тому, что будет выглядеть как «сохраненное состояние», но это просто то, что вы изменили глобальный объект, который служил шаблоном для начального состояния.