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

Javascript 渲染后记录状态的结果相同,javascript,reactjs,react-hooks,setstate,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 newTabl

我知道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调用是异步的,不会直接更新。 参考:


阅读此答案中的更多内容:

问题是您的第一个
useffect
,因为您删除了
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
(请参阅),其中所有数组的值均为“蓝色”,并将一直保持到卸载为止

您应该知道,在每个渲染上都执行组件体,因此在您的情况下,每个函数都有一个新实例

这同样适用于应作为参考的
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]);

使用React方法创建的变量是具有特殊行为的特殊对象;)

函数引用变量
,它是一个特殊的React(“state”)对象。看起来,在这种情况下,函数总是获取变量的初始状态-否则它至少会在下一次渲染时显示不同的值

如果使用常规表作为变量,您将得到预期结果。我认为您的代码的这个更改版本很好地证明了这一点:

import React,{useState,useffect}来自“React”;
从“react dom”导入react dom;
导入“/style.css”;
常量应用=()=>{
设currentShapePosition=2;
让tableDemo=[“蓝色”、“蓝色”、“蓝色”、“蓝色”、“蓝色”];
函数setTableDemo(newTable){
tableDemo=newTable.slice();
可设置(tableDemo);//用于触发渲染
}
//用于渲染目的(保留原始代码结构)
const[table,setTable]=使用状态(tableDemo);
useffect(()=>{
printTable();
window.addEventListener(“向下键”,handleKeyPress);
//eslint禁用下一行react HOOK/deps
}, []);
useffect(()=>{
console.log(“呈现”,表);
});
函数printTable(){
设newTable=[…表];
//设newTable=table;
新表[currentShapePosition]=“红色”;
setTableDemo(新表);
console.log(“printTable newTable”,newTable);//{
常数[currentPosition,setPosition]=使用状态(2);
const table=useRef([“蓝色”、“蓝色”、“红色”、“蓝色”、“蓝色”);
功能手柄按键(事件){
让我们重新定位;
控制台日志(“当前位置:”,当前位置);
表.当前[currentPosition]=“蓝色”;
开关(事件键){
案例“左”://IE/边缘特定值
案例“箭头左”:
newPosition=当前位置-1;
打破
案例“右”://IE/边缘特定值
案例“ArrowRight”:
新位置=当前位置+1;
打破
违约:
}
表.当前[新位置]=“红色”;
console.log(“newPosition:”,newPosition);
console.log(“表:”,表.current);
//触发新渲染
设置位置(新位置);
}
useffect(()=>{
window.addEventListener(“向下键”,handleKeyPress);
return()=>window.removeEventListener(“keydown”,handleKeyPress);
});
返回(
);
};
const root=document.getElementById(“根”);
ReactDOM.render(,root);


当然,最好的解决方案是使用相关类动态呈现
元素,而不使用任何表。但这是另一回事,超越了这个问题。

我确实理解useState是异步的,因此数组可能不会立即更改,但是,在printTable函数中,即使在服务器关闭后,console.log结果也是相同的al重新呈现。事实上,即使在组件更新之后,例如使用useEffect,在打印表函数中,console.log结果是相同的。请阅读问题,我投了反对票,因为它没有回答问题。可能重复的不是重复的,人们请阅读问题如果它不是重复的,它是缺失的…它有一个例子在代码沙盒中,我相信这是由于闭包造成的,但我承认我不是100%赞同它。我在控制台中看到这些值:printTable newTable[“蓝”、“蓝”、“红”、“蓝”、“蓝”]printTable[“蓝”、“蓝”、“蓝”、“蓝”、“蓝”]render[“蓝”、“蓝”、“蓝”、“红”、“蓝”]我认为在第二种情况下,它不应该总是“蓝色”.可能是的。在问我的问题之前,我读了一些关于闭包的书,但闭包的工作方式目前我还不是很清楚。我发现了一个bug,它被认为是一个bug,如果答案有用的话,请随意接受。现在唯一缺少的是
useffect
hook的返回值,它应该是一个删除e键事件。
return()=>window.removeEventListener(“keydown”,handleKeyPress);
谢谢,出于同样的原因,还应该有一个
currentPosition
的参考
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]);
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);