2 回答
TA贡献1744条经验 获得超4个赞
对于您的示例,没有区别。但在某些情况下,这很重要。
const [val, setVal] = useState(0);
return (<div onClick={() => setVal(val + 1)}>
<span onClick={() => setVal(val + 1)}>{val}</span>
</div>);
每次点击只会将值增加 1 (0 -> 1 -> 2 -> 3)。现场示例:
const {useState} = React;
function Example() {
const [val, setVal] = useState(0);
return (<div onClick={() => setVal(val + 1)}>
<span onClick={() => setVal(val + 1)}>{val}</span>
</div>);
}
ReactDOM.render(<Example/>, document.getElementById("root"));
<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>
const [val, setVal] = useState(0);
return (<div onClick={() => setVal(oldVal => oldVal + 1)}>
<span onClick={() => setVal(oldVal => oldVal + 1)}>{val}</span>
</div>);
每次点击将增加 2 个值(0 -> 2 -> 4 -> 6)。现场示例:
const {useState} = React;
function Example() {
const [val, setVal] = useState(0);
return (<div onClick={() => setVal(oldVal => oldVal + 1)}>
<span onClick={() => setVal(oldVal => oldVal + 1)}>{val}</span>
</div>);
}
ReactDOM.render(<Example/>, document.getElementById("root"));
<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>
TA贡献1874条经验 获得超12个赞
因为这些对状态设置器的调用在点击处理程序中,所以保证在处理另一次点击之前重新渲染您的组件。因此,在大多数情况下,您不必使用 setter 的回调版本,您可以直接使用现有状态。(即使在并发模式下。)(请注意,如果您在多个位置处理相同的单击 [例如,一个元素及其后代],并且您希望这两个处理程序都更新值,那是另一回事- 有关示例,请参见skyboyer 的答案。)
并非所有事件都是如此(例如,mousemove 没有此保证),但对于单击却是正确的。
我在这个线程的推特上从 Dan Abramov 那里得到了这些信息。当时,像点击这样有这种保证的事件被称为“交互式”事件。此后,该名称已更改为“离散”事件。你可以在 React 代码的这个源文件中找到一个列表。
当然,并不是所有的状态变化都直接来自事件。假设您的代码中有一个单击处理程序,它连续执行几个 ajax 调用,并且它会更新您的值以响应完成每个调用。即使您尝试非常彻底,直接更新版本也会不正确useCallback;回调版本将是正确的:
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>
出于这个原因,每当我设置一个基于先前值的新值时,我都会使用回调版本,除非它是专用处理click程序或类似的处理程序,在这种情况下我可以直接使用。
回到事件,并发模式使非“离散”事件更容易叠加。在 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>
也许这只是我没有在足够多的平台上进行测试,但我无法让它们在 React 的“传统模式”中出现分歧。但是,将相同的代码与具有并发模式的实验版本一起使用,通过在其上快速摆动鼠标,很容易使directValue滞后于它,这表明事件处理程序在渲染之间运行了不止一次。callbackValuemanualValue
添加回答
举报
