React UseState - 使用以前的状态与不使用以前的状态



我想知道下面的两个例子有什么区别。 在一个示例中,我使用以前的状态,在另一个示例中,我直接使用当前值。 他们都给了我相同的结果。 在哪些情况下,我应该使用一种方式而不是另一种方式? 提前谢谢。

import React,{useState} from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [count, setCount] = useState(0);
const [count2, setCount2] = useState(0);
return (
<div className="App">
Count: {count}
<button onClick={() => setCount(0)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
<br/>
<br/>
Count: {count2}
<button onClick={() => setCount2(0)}>Reset</button>
<button onClick={() => setCount2(count2 - 1)}>-</button>
<button onClick={() => setCount2(count2 + 1)}>+</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

由于对状态资源库的这些调用位于单击处理程序中,因此可以保证在处理另一次单击之前重新呈现组件。因此,在大多数情况下,您不必使用 setter 的回调版本,您可以直接使用现有状态。(即使在并发模式下。(请注意,如果您在多个位置(例如,一个元素及其后代(处理相同的单击,并且您希望这两个处理程序都更新值,那就是另一回事 了 - 请参阅Skyboyer的答案以获取示例。

并非所有事件都是如此(例如,鼠标移动没有此保证(,但对于点击也是如此。

我在这个线程的推特上从丹·阿布拉莫夫那里得到了这些信息。当时,具有此保证的点击等事件称为"交互式"事件。此后,该名称已更改为"离散"事件。您可以在 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处理程序或类似版本,在这种情况下,我可能会直接使用。


回到事件,并发模式使非"离散"事件更容易堆叠。在当前版本的 React on cdnjs.com (v16.10.2( 中,我无法让以下内容具有不同的数字来表示directValuecallbackValuemanualValue

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滞后于callbackValue并通过在其上快速摇晃鼠标来manualValue,这表明事件处理程序在渲染之间运行了不止一次。

对于您的示例,没有区别。但在某些情况下,这很重要。

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>

相关内容

  • 没有找到相关文章

最新更新