Because those calls to the state setter are in click handlers, your component is guaranteed to be re-rendered before another click is processed.
(由于对状态设置程序的那些调用是在单击处理程序中进行的,因此可以确保在处理另一次单击之前重新呈现组件。)
For that reason, in most cases you don't have to use the callback version of the setter, you can directly use your existing state.(因此,在大多数情况下,您不必使用setter的回调版本,您可以直接使用现有状态。)
(Even in concurrent mode.) (Note that if you handle the same click in more than one place [an element and a descendant of it, for instance], and you want both of those handlers to update the value, that's a different matter — see skyboyer's answer for an example of that.)((即使在并发模式下也是如此。)(请注意,如果您在多个地方(例如,一个元素及其后代)处理相同的单击,并且希望这两个处理程序都更新该值, 那就是另一回事了。 —有关示例,请参见skyboyer的答案 。))
This is not true for all events (mousemove, for instance, does not have this guarantee), but it's true for click.
(并非对所有事件都是如此(例如,mousemove没有此保证),但对于点击而言,它是正确的。)
I got this information from Dan Abramov on twitter in this thread .
(我是从Dan Abramov在Twitter上通过此线程获得此信息的。)
At the time, events like click that had this guarantee were called "interactive" events.(当时,具有这种保证的单击之类的事件称为“互动”事件。)
The name has since changed to "discrete" events.(此后名称已更改为“离散”事件。)
You can find a list in this source file in the React code.(您可以在React代码的此源文件中找到列表。)
Of course, not all state changes come directly from events.
(当然,并非所有状态更改都直接来自事件。)
Suppose you have a click handler in your code that does a couple of ajax calls in series and, it happens, updates your value in response to completing each of them.(假设您的代码中有一个单击处理程序,该处理程序会依次执行几个ajax调用,并且恰好在完成每个操作时都会更新您的值。)
The direct update version will be incorrect even if you've tried to be really thorough with useCallback
;(即使您尝试通过useCallback
进行彻底的useCallback
,直接更新版本也不正确;)
the callback version will be correct:(回调版本将是正确的:)
const {useState, useCallback} = React; function ajaxGet() { return new Promise(resolve => setTimeout(resolve, 10)); } function Example() { const [directValue, setDirectValue] = useState(0); const [callbackValue, setCallbackValue] = useState(0); const doThis = useCallback(() => { setDirectValue(directValue + 1); setCallbackValue(callbackValue => callbackValue + 1); }, [directValue, callbackValue]); const doThat = useCallback(() => { setDirectValue(directValue + 1); setCallbackValue(callbackValue => callbackValue + 1); }, [directValue, callbackValue]); const handleFirstFulfilled = useCallback(() => { // ... doThis(); // ... return ajaxGet("something else"); }, [doThis]); const handleSecondFulfilled = useCallback(() => { // ... doThat(); // ... }, [doThat]); const handleClick = useCallback(() => { ajaxGet("something") .then(handleFirstFulfilled) .then(handleSecondFulfilled) .catch(error => { // ...handle/report error... }); }, [handleFirstFulfilled, handleSecondFulfilled]); const cls = directValue !== callbackValue ? "diff" : ""; return ( <div className={cls}> <input type="button" onClick={handleClick} value="Click Me" /> <div> Direct: {directValue} </div> <div> Callback: {callbackValue} </div> </div> ); } ReactDOM.render(<Example />, document.getElementById("root"));
.diff { color: #d00; }
<div id="root"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
(Disclaimer: That code may be utter rubbish. The point is to see the effect despite having tried to memoize everything. :-) )
((免责声明:该代码可能完全是垃圾。重点是尽管试图记住所有内容,但还是要看到效果。:-)))
For that reason, any time I'm setting a new value that's based on the previous value, I use the callback version unless it's a dedicated click
handler or similar, in which case I may go direct.
(因此, 无论何时我基于先前的值设置一个新值,我都会使用回调版本,除非它是专用的click
处理程序或类似的处理程序,在这种情况下,我可能会直接使用。)
Getting back to events, concurrent mode makes non-"discrete" events easier to stack up.
(回到事件,并发模式使非“离散”事件更易于堆积。)
In the current version of React on cdnjs.com (v16.10.2), I cannot get the following to have different numbers for directValue
, callbackValue
, and manualValue
:(在cdnjs.com(v16.10.2)的当前版本的React中,我无法获得以下值来为directValue
, callbackValue
和manualValue
设置不同的数字:)
const {useState} = React; // Obviously this is a hack that only works when `Example` is used only once on a page let manualValue = 0; const manualDisplay = document.getElementById("manualDisplay"); function Example() { const [directValue, setDirectValue] = useState(0); const [callbackValue, setCallbackValue] = useState(0); const handleMouseMove = () => { setDirectValue(directValue + 1); setCallbackValue(callbackValue => callbackValue + 1); manualDisplay.textContent = ++manualValue; }; const different = directValue !== callbackValue || directValue !== manualValue; document.body.className = different ? "diff" : ""; return ( <div onMouseMove={handleMouseMove}> Move the mouse rapidly over this element. <div> Direct: {directValue} </div> <div> Callback: {callbackValue} </div> </div> ); } const ex = <Example />; if (ReactDOM.createRoot) { document.body.insertAdjacentHTML("beforeend", "<div>Concurrent</div>"); ReactDOM.createRoot(document.getElementById("root")).render(ex); } else { ReactDOM.render(ex, document.getElementById("root")); document.body.insertAdjacentHTML("beforeend", "<div>Legacy</div>"); }
.diff { color: #d00; }
<div id="root"></div> <div> Manual: <span id="manualDisplay">0</span> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
Maybe that's just me not testing on enough platforms, but I can't get them to diverge in React's "legacy mode."
(也许那只是我没有在足够的平台上进行测试,但是我无法让它们在React的“传统模式”中脱节。)
But , using that same code with the experimental release with concurrent mode, it's fairly easy to get the directValue
to lag behind the callbackValue
and manualValue
by waggling the mouse quickly over it, indicating that the event handler is running more than once between renders.(但是 ,将相同的代码与并发模式的实验版本一起使用,通过在其上快速移动鼠标来使directValue
落后于callbackValue
和manualValue
相当容易,这表明事件处理程序在渲染之间多次运行。)