渲染后记录状态的结果相同

时间:2019-10-10 18:35:36

标签: javascript reactjs react-hooks setstate

我了解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中更新,但是没有重新渲染/组件更新。

我想了解为什么,在多次重新渲染后,在控制台内部的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 个答案:

答案 0 :(得分:2)

问题是您的第一个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的{​​{1}}的 first 实例(请参阅closures ),其中所有数组的值均为“ blue”,它将一直保持这种状态,直到卸载为止。

您应该注意,在每个渲染器上执行了组件主体,因此,在您的情况下,每个函数都有一个新实例。

addEventListener也应作为参考。

要解决此问题,请删除currentPosition注释并附带警告:

eslint

Edit Q-58329134-LogTable

答案 1 :(得分:1)

使用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“状态”对象来保留“源状态值”

基本上,最好只使用React“状态”对象来保留“源状态值”,以用于触发重新渲染。在这种特定情况下,源状态值为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>元素,而无需任何表。但这是一个不同的故事,除了这个问题。

答案 2 :(得分:0)

useState调用是异步的,它们不会直接更新。 参考:https://reactjs.org/docs/hooks-state.html

了解更多此答案:useState set method not reflecting change immediately