lik*_*ern 25 reactjs react-hooks
我正在阅读有关功能更新的React Hook 文档并查看此引用:
“+”和“-”按钮使用函数形式,因为更新的值是基于之前的值
但是我看不出需要功能更新的目的是什么,它们与直接使用旧状态计算新状态有什么区别。
为什么 React useState Hook 的更新器函数根本需要函数式更新表单? 我们可以清楚地看到差异的示例有哪些(因此使用直接更新会导致错误)?
例如,如果我从文档中更改此示例
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
</>
);
}
Run Code Online (Sandbox Code Playgroud)
count
直接更新:
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
</>
);
}
Run Code Online (Sandbox Code Playgroud)
我看不出任何行为差异,也无法想象计数不会更新(或不会更新)的情况。因为每当计数发生变化时,onClick
就会调用新的闭包 for ,捕获最新的count
.
Ale*_*sen 21
React 中的状态更新是异步的。因此,count
下次更新时可能会有旧值。例如,比较这两个代码示例的结果:
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => {
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1)}
}>+</button>
</>
);
}
Run Code Online (Sandbox Code Playgroud)
和
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => {
setCount(count + 1);
setCount(count + 1)}
}>+</button>
</>
);
}
Run Code Online (Sandbox Code Playgroud)
Van*_*pps 19
\xe2\x80\x9cstate更新在 React\xe2\x80\x9d 答案中是异步的,下面的一些评论也是如此。在我进一步深入研究之前,我的想法也是错误的。你是对的,这很少需要。
\n功能状态更新背后的关键思想是,新状态所依赖的状态可能已经过时。状态如何变得陈旧?让\xe2\x80\x99s 消除一些关于它的误解:
\n\n\n注意:React 仅在通常安全的情况下批量更新。例如,React 确保对于每个用户启动的事件(例如单击或按键),DOM 在下一个事件之前完全更新。例如,这可以确保禁用提交的表单可以\xe2\x80\x99 提交两次。
\n
@Ich 在评论中提出了一个很好的问题。即使您在处理一个事件和下一个事件之间重新渲染,它们是否都使用引用相同状态的相同处理程序,因为它们源自相同的渲染?
\n这涉及到他们不常谈论的 React 事件处理的细节。React 17 发行说明中提到了这一点。
\n在 React 17 中,事件处理程序不再注册在 上document
,而是注册在根节点上。我记得读过这篇文章并感到困惑。document
为什么首先要使用事件处理程序?为什么当我们编写 时<button onClick="...">
,处理程序不在DOMElement上button
?
答案似乎是(除其他外)React 无法立即安全地运行处理程序。事实上,它不一定知道要运行哪个处理程序(如果有的话)!romain-trotard 的要点涉及到实现细节。
\n无论onClick
树中有多少个,React 都只会click
在树的顶部注册一个处理程序。该处理程序只是一个垫片,它将事件分派到 React 中的队列中。
这样 React 就可以按照自己的节奏处理事件。每个渲染最多只能处理 1 个用户启动的事件。队列中的其他人必须等待下一次渲染,或者之后的渲染,等等。这样,他们每个人都会得到一个新的处理程序。
\n假设您有一个增加状态的按钮。您有一个涡轮鼠标,每 1 毫秒点击一次,并且在下一次渲染之前敲击该按钮 10 次。
\nReact 将 10 个点击事件放入其事件队列中。它将其中一个出列并处理它,从而导致重新渲染。
\n重新渲染完成后,React 会注意到队列不为空,并将下一个队列出队并使用使用新状态的新处理程序来处理它。它再次重新渲染。
\n重复此操作直到队列为空。最后有 10 个渲染和 10 个具有不同状态闭包的不同处理程序。
\n我能想到的主要有3种情况:
\n这是已经提到的情况,您在同一个处理程序中多次设置相同的状态,并依赖于先前的状态。正如您所指出的,这种情况非常人为,因为这显然看起来是错误的:
\n <button\n onClick={() => {\n setCount(count + 1);\n setCount(count + 1);\n }}\n >+</button>\n
Run Code Online (Sandbox Code Playgroud)\n更合理的情况是调用多个函数,每个函数都会更新同一状态并依赖于先前的状态。但是\xe2\x80\x99s仍然很奇怪,\xe2\x80\x99d进行所有计算然后设置一次状态更有意义。
\n例如:
\n <button\n onClick={() => {\n doSomeApiCall().then(() => setCount(count + 1));\n }}\n >+</button>\n
Run Code Online (Sandbox Code Playgroud)\n这并不是那么明显的错误。doSomeApiCall
在您调用和解决之间可以更改状态。在这种情况下,状态更新确实是异步的,但你是这样做的,而不是 React!
函数形式解决了这个问题:
\n <button\n onClick={() => {\n doSomeApiCall().then(() => setCount((currCount) => currCount + 1));\n }}\n >+</button>\n
Run Code Online (Sandbox Code Playgroud)\nG Gallegos 的回答指出了这一点useEffect
,而letvar 的回答则指出了这useEffect
一点requestAnimationFrame
。如果您要根据 中的先前状态更新状态useEffect
,则将该状态放入依赖项数组中(或不使用依赖项数组)会导致无限循环。请改用函数形式。
您不需要\xe2\x80\x99t 需要基于先前状态进行状态更新的功能形式,只要您执行以下操作:1. 在用户触发的事件处理程序中2. 每个处理程序每个状态一次3. 同步。如果您违反了其中任何一个条件,则需要进行功能更新。
\n有些人可能更喜欢始终使用功能更新,因此您不必担心这些情况。为了清楚起见,其他人可能更喜欢较短的形式,因为这样做是安全的,这对于许多处理程序来说都是如此。此时\xe2\x80\x99是个人喜好/代码风格。
\n我在 Hooks 之前学习了 React,当时只有类组件才有状态。在类组件中,\xe2\x80\x9c在同一个处理程序中进行多次状态更新\xe2\x80\x9d看起来\xe2\x80\x99t看起来显然是错误的:
\n <button\n onClick={() => {\n this.setState({ count: this.state.count + 1 });\n this.setState({ count: this.state.count + 1 });\n }}\n >+</button>\n
Run Code Online (Sandbox Code Playgroud)\n由于状态是实例变量而不是函数参数,因此这看起来很好,除非您知道setState
在同一处理程序中批量调用。
事实上,在 React <= 17 中,这可以正常工作:
\n setTimeout(() => {\n this.setState({ count: this.state.count + 1 });\n this.setState({ count: this.state.count + 1 });\n }, 1000);\n
Run Code Online (Sandbox Code Playgroud)\n由于它\xe2\x80\x99 不是事件处理程序,React 在每次调用后都会重新渲染setState
。
React 18 针对这种情况和类似情况引入了批处理。这是一个有用的性能改进。缺点是它破坏了依赖上述行为的类组件。
\neha*_*hab 10
我已经回答了类似的问题,它已经关闭,因为这是规范的问题 - 我不知道,在查看答案后,我决定在这里重新发布我的答案,因为我认为它增加了一些价值。
如果您的更新取决于在状态中找到的先前值,那么您应该使用函数形式。如果在这种情况下您不使用函数形式,那么您的代码有时会崩溃。
为什么会破裂以及何时破裂
React 功能组件只是闭包,闭包中的状态值可能已过时 - 这意味着闭包内的值与该组件的 React 状态值不匹配,这可能发生在以下情况:
1-异步操作(在本例中单击“慢速添加”,然后多次单击“添加”按钮,稍后您将看到单击“慢速添加”按钮时状态已重置为闭包内的状态)
const App = () => {
const [counter, setCounter] = useState(0);
return (
<>
<p>counter {counter} </p>
<button
onClick={() => {
setCounter(counter + 1);
}}
>
immediately add
</button>
<button
onClick={() => {
setTimeout(() => setCounter(counter + 1), 1000);
}}
>
Add
</button>
</>
);
};
Run Code Online (Sandbox Code Playgroud)
2-当您在同一个闭包中多次调用更新函数时
const App = () => {
const [counter, setCounter] = useState(0);
return (
<>
<p>counter {counter} </p>
<button
onClick={() => {
setCounter(counter + 1);
setCounter(counter + 1);
}}
>
Add twice
</button>
</>
);
}
Run Code Online (Sandbox Code Playgroud)
我最近偶然发现了这个需求。例如,假设您有一个组件,该组件用一定数量的元素填充数组,并且能够根据某些用户操作附加到该数组(例如在我的情况下,我以用户身份一次加载 10 个项目)继续向下滚动屏幕。代码看起来有点像这样:
function Stream() {
const [feedItems, setFeedItems] = useState([]);
const { fetching, error, data, run } = useQuery(SOME_QUERY, vars);
useEffect(() => {
if (data) {
setFeedItems([...feedItems, ...data.items]);
}
}, [data]); // <---- this breaks the rules of hooks, missing feedItems
...
<button onClick={()=>run()}>get more</button>
...
Run Code Online (Sandbox Code Playgroud)
显然,您不能只将 feedItems 添加到 useEffect 钩子中的依赖项列表,因为您在其中调用了 setFeedItems,因此您会陷入循环。
救援功能更新:
useEffect(() => {
if (data) {
setFeedItems(prevItems => [...prevItems, ...data.items]);
}
}, [data]); // <--- all good now
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
7536 次 |
最近记录: |