渲染后记录状态的结果相同
Same result on logging the state after render
我明白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 中更新状态,但是没有 re-rendering/component 更新。
我想明白为什么在第一种情况下newTable = [...table]
在函数内部console.log结果在几次重新渲染后是相同的。
为什么 在第二种情况下 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);
useState 调用是异步的,它们不会直接更新。
参考:https://reactjs.org/docs/hooks-state.html
在这个答案中阅读更多内容:
问题是您的第一个 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
),其中所有数组的值都是 "blue" 并且它将保持这种状态直到卸载。
您应该知道,在每次渲染时 组件主体都会执行,因此在您的情况下,每个函数都有一个新实例。
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 方法创建的变量是具有特殊行为的特殊对象 ;)
您的函数引用变量 table
,它是一个特殊的 React ("state") 对象。看起来,在这种情况下,函数总是获取变量的初始状态——否则它至少会在下一次渲染时显示不同的值。
如果您使用常规 table 作为变量,您将得到预期的结果。我认为您的代码的这个更改版本很好地证明了这一点:
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" 对象来保持 "source state values"
基本上最好只使用 React "state" 对象来保持 "source state values",用于触发重新渲染。在这种特定情况下,源状态值为 currentShapePosition
。 table 本身不应该改变 - 只有这个 table 的一些特定元素。所以实际上,根据 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>
元素,其中包含相关的 class,而不包含任何 table。但这是一个不同的故事,超越这个问题。
我明白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 中更新状态,但是没有 re-rendering/component 更新。
我想明白为什么在第一种情况下newTable = [...table]
在函数内部console.log结果在几次重新渲染后是相同的。
为什么 在第二种情况下 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);
useState 调用是异步的,它们不会直接更新。 参考:https://reactjs.org/docs/hooks-state.html
在这个答案中阅读更多内容:
问题是您的第一个 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
),其中所有数组的值都是 "blue" 并且它将保持这种状态直到卸载。
您应该知道,在每次渲染时 组件主体都会执行,因此在您的情况下,每个函数都有一个新实例。
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 方法创建的变量是具有特殊行为的特殊对象 ;)
您的函数引用变量 table
,它是一个特殊的 React ("state") 对象。看起来,在这种情况下,函数总是获取变量的初始状态——否则它至少会在下一次渲染时显示不同的值。
如果您使用常规 table 作为变量,您将得到预期的结果。我认为您的代码的这个更改版本很好地证明了这一点:
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" 对象来保持 "source state values"
基本上最好只使用 React "state" 对象来保持 "source state values",用于触发重新渲染。在这种特定情况下,源状态值为 currentShapePosition
。 table 本身不应该改变 - 只有这个 table 的一些特定元素。所以实际上,根据 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>
元素,其中包含相关的 class,而不包含任何 table。但这是一个不同的故事,超越这个问题。