我对 React 完全陌生,并试图了解与 prevState 相关的课程,特别是计数器。我尝试在单击另一个按钮时创建以计数器编号命名的按钮,但它的行为很奇怪,我不知道是什么原因造成的。
这是表现奇怪的部分。
const addButton = () => {
for (let i = 0; i < 5; i++) {
console.log(`Iteration number: ${i + 1}`);
setCounter(prevCounter => {
const newButton = {
id: `button${prevCounter}`,
label: `Button ${prevCounter}`
};
setButtons(prevButtons => [...prevButtons, newButton]);
return prevCounter +1;
});
}};
如果需要的话,所有代码。
import React, { useState, useEffect } from 'react';
function App() {
const [Counter, setCounter] = useState(1);
const [buttons, setButtons] = useState([]);
const addButton = () => {
for (let i = 0; i < 5; i++) {
console.log(`Iteration number: ${i + 1}`);
setCounter(prevCounter => {
const newButton = {
id: `button${prevCounter}`,
label: `Button ${prevCounter}`
};
setButtons(prevButtons => [...prevButtons, newButton]);
return prevCounter +1;
});
}};
return (
<div className="p-4">
<h1 className="text-2xl font-bold mb-4">Dynamic Button Creator</h1>
<button
onClick={addButton}
className="bg-blue-500 text-white px-4 py-2 rounded mb-4"
>
Add Button
</button>
<div>
{buttons.map(button => (
<button
key={button.id}
id={button.id}
onClick={addButton}
className="bg-green-500 text-white px-4 py-2 rounded mb-2 mr-2"
>
{button.label}
</button>
))}
</div>
</div>
);
}
export default App;
主要问题源于您在
addButton
回调中使用 for 循环来同步排队一堆 React 状态更新,并且应用程序在 React.StrictMode
组件中呈现,该组件双重调用某些生命周期方法和钩子回调作为调试工具来帮助发现错误。
具体参见修复开发中双重渲染发现的bug:
React 假设你编写的每个组件都是纯函数。这 意味着您编写的 React 组件必须始终返回相同的 JSX 给定相同的输入(道具、状态和上下文)。
违反此规则的组件会出现不可预测的行为并导致错误。到 帮助您意外发现不纯的代码,严格模式会调用您的一些 函数(仅那些应该是纯的)在开发中两次。 这包括:
- 您的组件函数体(仅顶层逻辑,因此不包括事件处理程序内的代码)
- 传递给
的函数、useState
函数、set
或useMemo
useReducer
- 一些类组件方法,如构造函数、渲染、shouldComponentUpdate(查看整个列表)如果函数是纯函数, 运行两次不会改变其行为,因为纯函数 每次都会产生相同的结果。但是,如果函数不纯 (例如,它会改变接收到的数据),运行两次往往会 引人注目(这就是它不纯的原因!)这可以帮助您发现 并尽早修复错误。
我已经强调了与您的代码相关的要点。
setCounter
回调不是纯粹的,因为它正在调用 setButtons
状态更新器。
状态更新器被调用的频率比您预期的要高,然后在后续更新中会复制以前的状态值。这种重复会导致重复的 React 键问题,因为多个按钮元素具有相同的
counter
值用于计算键。
如果您只是尝试在每次单击按钮时“添加 a 按钮”,那么以下实现应该足够了:
const addButton = () => {
// Add a new button to the buttons array using the current counter value
setButtons((buttons) =>
buttons.concat({
id: `button${counter}`,
label: `Button ${counter}`,
})
);
// Increment the counter value
setCounter((counter) => counter + 1);
};
function App() {
const [counter, setCounter] = React.useState(1);
const [buttons, setButtons] = React.useState([]);
const addButton = () => {
setButtons((buttons) =>
buttons.concat({
id: `button${counter}`,
label: `Button ${counter}`,
})
);
setCounter((counter) => counter + 1);
};
return (
<div className="p-4">
<h1 className="text-2xl font-bold mb-4">Dynamic Button Creator</h1>
<button
onClick={addButton}
className="bg-blue-500 text-white px-4 py-2 rounded mb-4"
>
Add Button
</button>
<div>
{buttons.map((button) => (
<button
key={button.id}
id={button.id}
onClick={addButton}
className="bg-green-500 text-white px-4 py-2 rounded mb-2 mr-2"
>
{button.label}
</button>
))}
</div>
</div>
);
}
const rootElement = document.getElementById("root");
const root = ReactDOM.createRoot(rootElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<div id="root" />
如果您想每次点击添加多个按钮,那么循环应该将上述逻辑包装在一个循环中,并且仍然独立地将状态更新排入队列。
const addButton = () => {
// Start loop at current counter value,
// use loop counter i for button value
for (let i = counter; i < counter + 3; i++) {
setButtons((buttons) =>
buttons.concat({
id: `button${i}`,
label: `Button ${i}`,
})
);
setCounter((counter) => counter + 1);
}
};
function App() {
const [counter, setCounter] = React.useState(1);
const [buttons, setButtons] = React.useState([]);
const addButton = () => {
for (let i = counter; i < counter + 3; i++) {
setButtons((buttons) =>
buttons.concat({
id: `button${i}`,
label: `Button ${i}`,
})
);
setCounter((counter) => counter + 1);
}
};
return (
<div className="p-4">
<h1 className="text-2xl font-bold mb-4">Dynamic Button Creator</h1>
<button
onClick={addButton}
className="bg-blue-500 text-white px-4 py-2 rounded mb-4"
>
Add 3 Buttons
</button>
<div>
{buttons.map((button) => (
<button
key={button.id}
id={button.id}
onClick={addButton}
className="bg-green-500 text-white px-4 py-2 rounded mb-2 mr-2"
>
{button.label}
</button>
))}
</div>
</div>
);
}
const rootElement = document.getElementById("root");
const root = ReactDOM.createRoot(rootElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<div id="root" />