Тот же результат при регистрации состояния после рендера - PullRequest
3 голосов
/ 10 октября 2019

Я понимаю, что useState является асинхронным. Пожалуйста, прочитайте полный вопрос, прежде чем ответить.

Я пытаюсь изменить элемент в массиве с useState, но он не работает, как ожидалось.

Массив ифункция для его изменения:

const [table, setTable] = useState(['blue', 'blue', 'blue', 'blue', 'blue']);

let currentShapePosition = 2;

function printTable() {
  let newTable = [...table];
  // let newTable = table;
  newTable[currentShapePosition] = 'red';
  setTable(newTable);
  console.log('printTable newTable', newTable); // <-- the result is as expected
  // log => printTable newTable ["blue", "blue", "red", "blue", "blue"]
  console.log('printTable table', table); // <--- The problem is here. I don't get why the array never update
  // log => printTable newTable ["blue", "blue", "blue", "blue", "blue"]
}

Поскольку useState является асинхронным, я понимаю, что массив может измениться не сразу, но внутри функции printTable результат console.log являетсято же самое даже после нескольких повторных рендерингов .

Когда вместо: let newTable = [...table]

я делаю это: let newTable = table

Затем состояние обновляется внутриconsole.log сгенерирован в функции, но затем нет повторного рендеринга / обновления компонента.

Я хотел бы понять, почему в первом случае newTable = [...table] внутри функции консоли. результат журнала остается тем же после нескольких повторных рендеров. И почему во втором случае newTable = table нет повторного рендеринга компонента, несмотря на setTable(newTable).

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./style.css";

const App = () => {
  let currentShapePosition = 2;

  const [table, setTable] = useState(["blue", "blue", "blue", "blue", "blue"]);

  useEffect(() => {
    printTable();
    window.addEventListener("keydown", handleKeyPress);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    console.log("render", table);
  });

  function printTable() {
    let newTable = [...table];
    // let newTable = table;
    newTable[currentShapePosition] = "red";
    setTable(newTable);
    console.log("printTable newTable", newTable); // <-- the result is as expected
    console.log("printTable table", table); // <--- The problem is here. I don't get why the initial value never change
  }

  function handleKeyPress(event) {
    switch (event.key) {
      case "Left": // IE/Edge specific value
      case "ArrowLeft":
        moveShape(-1);
        break;
      case "Right": // IE/Edge specific value
      case "ArrowRight":
        moveShape(1);
        break;
      default:
        return;
    }
  }

  function moveShape(direction) {
    currentShapePosition += direction;
    printTable();
  }

  return (
    <table className="tetris-table">
      <tbody>
        <tr>
          <td className={table[0]} />
          <td className={table[1]} />
          <td className={table[2]} />
          <td className={table[3]} />
          <td className={table[4]} />
        </tr>
      </tbody>
    </table>
  );
};

const root = document.getElementById("root");

ReactDOM.render(<App />, root);

Edit fervent-field-787ky

Ответы [ 3 ]

2 голосов
/ 10 октября 2019

Проблема ваша первая useEffect, поскольку вы удалили предупреждение eslint, оно скрыло потенциальную ошибку.

useEffect(() => {
  printTable();
  window.addEventListener('keydown', handleKeyPress);
  //   v hidden bug, you should consider the warnings
  // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

Что здесь произошло, так это то, что на компонентное монтирование первый экземпляр handleKeyPress, присвоенный addEventListener (см. closures), где весь массив имел значение "синий", и он будет оставаться таким доthe unmount.

Вы должны знать, что при каждом рендеринге выполняется тело компонента, поэтому в вашем случае есть новый экземпляр каждой функции.

То же самоеидет за currentPosition, который должен быть ссылкой.

Чтобы исправить это, удалите комментарий eslint и перейдите с предупреждениями:

useEffect(() => {
  const printTable = () => {
    let newTable = [...table];
    newTable[currentPosition.current] = 'red';
    setTable(newTable);
    console.log('closure', table);
  };

  function handleKeyPress(event) {
    switch (event.key) {
      case 'Left': // IE/Edge specific value
      case 'ArrowLeft':
        moveShape(-1);
        break;
      case 'Right': // IE/Edge specific value
      case 'ArrowRight':
        moveShape(1);
        break;
      default:
        return;
    }
  }

  function moveShape(direction) {
    currentPosition.current += direction;
    printTable();
  }

  window.addEventListener('keydown', handleKeyPress);

  return () => window.removeEventListener('keydown', handleKeyPress);
}, [table]);

Edit Q-58329134-LogTable

1 голос
/ 11 октября 2019

Переменные, созданные с помощью методов React, являются специальными объектами со специальным поведением;)

Ваша функция ссылается на переменную table, которая является специальным объектом React ("состояние"). Похоже, в таком случае функция всегда получает начальное состояние переменной - в противном случае она будет показывать другие значения, по крайней мере, при следующих визуализациях.

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

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./style.css";

const App = () => {
  let currentShapePosition = 2;

  let tableDemo = ["blue", "blue", "blue", "blue", "blue"];
  function setTableDemo(newTable) {
    tableDemo = newTable.slice();
    setTable(tableDemo); // used to trigger rendering
  }

  // for rendering purposes (to keep the original code structure)
  const [table, setTable] = useState(tableDemo);

  useEffect(() => {
    printTable();
    window.addEventListener("keydown", handleKeyPress);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    console.log("render", table);
  });

  function printTable() {
    let newTable = [...table];
    // let newTable = table;
    newTable[currentShapePosition] = "red";
    setTableDemo(newTable);
    console.log("printTable newTable", newTable); // <-- the result is as expected
    console.log("printTable tableDemo", tableDemo); // <--- The problem is here. I don't get why the initial value never change
  }

  function handleKeyPress(event) {
    switch (event.key) {
      case "Left": // IE/Edge specific value
      case "ArrowLeft":
        moveShape(-1);
        break;
      case "Right": // IE/Edge specific value
      case "ArrowRight":
        moveShape(1);
        break;
      default:
        return;
    }
  }

  function moveShape(direction) {
    currentShapePosition += direction;
    printTable();
  }

  return (
    <table className="tetris-table">
      <tbody>
        <tr>
          <td className={table[0]} />
          <td className={table[1]} />
          <td className={table[2]} />
          <td className={table[3]} />
          <td className={table[4]} />
        </tr>
      </tbody>
    </table>
  );
};

const root = document.getElementById("root");

ReactDOM.render(<App />, root);


Используйте объекты React "state" только для сохранения "значений состояния источника"

По сути, лучше использовать объекты React «state» только для того, чтобы сохранить «значения состояния источника», используемые для запуска повторного рендеринга. В этом конкретном случае значение состояния источника currentShapePosition. Таблица как таковая не должна изменяться - только некоторые конкретные элементы этой таблицы. Таким образом, на самом деле код, согласно подходу React, может выглядеть следующим образом:

import React, { useState, useEffect, useRef } from "react";
import ReactDOM from "react-dom";
import "./style.css";

const App = () => {
  const [currentPosition, setPosition] = useState(2);
  const table = useRef(["blue", "blue", "red", "blue", "blue"]);

  function handleKeyPress(event) {
    let newPosition;

    console.log("currentPosition:", currentPosition);
    table.current[currentPosition] = "blue";
    switch (event.key) {
      case "Left": // IE/Edge specific value
      case "ArrowLeft":
        newPosition = currentPosition - 1;
        break;
      case "Right": // IE/Edge specific value
      case "ArrowRight":
        newPosition = currentPosition + 1;
        break;
      default:
    }
    table.current[newPosition] = "red";
    console.log("newPosition:", newPosition);
    console.log("table:", table.current);
    // trigger the new render
    setPosition(newPosition);
  }

  useEffect(() => {
    window.addEventListener("keydown", handleKeyPress);

    return () => window.removeEventListener("keydown", handleKeyPress);
  });

  return (
    <table className="tetris-table">
      <tbody>
        <tr>
          <td className={table.current[0]} />
          <td className={table.current[1]} />
          <td className={table.current[2]} />
          <td className={table.current[3]} />
          <td className={table.current[4]} />
        </tr>
      </tbody>
    </table>
  );
};

const root = document.getElementById("root");

ReactDOM.render(<App />, root);


Конечно, лучшее решение - это динамическая визуализация элементов <td> с соответствующим классом без какой-либо таблицы. ,Но за этим вопросом стоит другая история.

0 голосов
/ 10 октября 2019

вызовы useState являются асинхронными, они не обновляются напрямую. см .: https://reactjs.org/docs/hooks-state.html

Подробнее об этом ответе: метод использованияState, не отражающий немедленное изменение

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