我了解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);
答案 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
答案 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