Как я могу обновить это состояние, не изменяя массив? - PullRequest
1 голос
/ 14 февраля 2020

Я хочу знать, как не изменять состояние в следующем сценарии:

Я использую React Hooks. У меня есть TodoList, который перечисляет TodoItems. TodoItems можно щелкнуть, чтобы запустить функцию с именем toggle, которая переключает их свойство completed.

import React, { useState } from "react";
import TodoItem from "./TodoItem";

export default function TodoList() {
  const [todos, setTodos] = useState([
    { text: "Example", completed: false }
  ]);

  const toggle = index => {
    const newTodos = [...todos];
    newTodos[index].completed = !newTodos[index].completed; // Mutating state?
    setTodos(newTodos);
  };

  return (
    <>
      {todos.map((item, index) => (
        <TodoItem key={index} index={index} todo={item} toggle={toggle} />
      ))}
    </>
  );
}

Мне сказали, что это состояние мутирования:

Вы создайте копию массива todos, но вы по-прежнему изменяете элемент внутри массива:

newTodos[index].completed = !newTodos[index].completed;

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

Итак, как правильно справиться с этим без изменения состояния? Я попытался передать параметр prevTodos, но состояние не обновляется:

const toggle = index => {
  setTodos(prevTodos => {
    prevTodos[index].completed = !prevTodos[index].completed;
    return prevTodos;
  });
};

Обновление: Я просмотрел сообщения, которые вы все рекомендуется. Это решение, хотя мне интересно, есть ли более чистый способ express это:

const toggle = index => {
  setTodos(
    todos.map((todo, i) =>
      i === index ? { ...todo, completed: !todo.completed } : todo
    )
  );
};

Обновление 2: Спасибо всем. Я разместил решение ниже. StackOverflow не позволит мне принять его в течение 2 дней, но я сделаю это тогда. Принятый ответ на предложенный пост предполагает использование сторонней библиотеки, и она не использует React Hooks, поэтому я не считаю этот вопрос дубликатом.

Ответы [ 3 ]

0 голосов
/ 14 февраля 2020

Нашел решение. map вернет другой массив, и вы можете прослушать параметр индекса (в моем фрагменте ниже это i), чтобы прослушать текущий индекс и изменить объект, на который вы нацелены.

const toggle = index => {
  setTodos(
    todos.map((todo, i) =>
      i === index ? { ...todo, completed: !todo.completed } : todo
    )
  );
};

Эти темы помогли мне собрать это воедино:

Спасибо всем, кто помог.

0 голосов
/ 14 февраля 2020

Правильно, самый простой способ избежать этой проблемы - избежать изменения данных .

Одним из способов сделать это является использование функционального подхода, в частности, с использованием метода не мутирующего массива, такого как .map:

function TodoList() {
  const [todos, setTodos] = React.useState([{
    text: "Example",
    completed: true
  }]);

  const toggle = index => {
    const updatedTodos = todos.map((todo, i) => {
      if (i === index) {
        todo.completed = !todo.completed;
      }
      return todo;
    });
    setTodos(updatedTodos);
  };

  return (
    <React.Fragment > 
      {todos.map((item, index) => (
        <p
          key={item.text}
          onClick={() => toggle(index)}
          className={item.completed ? "completed" : ""}
        >
          {item.text}
        </p>
       ))}
    </React.Fragment>
  );
}

ReactDOM.render( < TodoList / > , document.getElementById('root'))
.completed {
  text-decoration: line-through;
}
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
0 голосов
/ 14 февраля 2020
const toggle = (index) => {
  const oldTodo = todos[index];
  setTodos(oldState => {
   oldState.splice(index, 1, {...oldTodo, checked: !oldTodo.checked})

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