有两个useState
组件中使用的钩子(Hook)。如果我们允许事件循环在修改状态之前“重排”,每个钩子(Hook)都会导致单独的重新渲染。
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
export default function App() {
const [foo, setFoo] = useState("initial_foo");
const [bar, setBar] = useState("initial_bar");
const onAction = useCallback(async () => {
await sleep(0); // this is the culprit
setFoo("updated_foo");
setBar("updated_bar");
}, []);
console.log(`rendering foo:${foo} bar:${bar}`);
return (
<>
<button onClick={onAction}>run test</button>
<div>{"foo: " + foo}</div>
<div>{"bar: " + bar}</div>
</>
);
}
带有 await sleep(0)
的控制台输出:rendering foo:initial_foo bar:initial_bar
rendering foo:updated_foo bar:initial_bar
rendering foo:updated_foo bar:updated_bar
如果我们删除 await sleep(0);
,一切正常:setFoo
和 setBar
导致一次重新渲染。没有
await sleep(0)
的控制台输出:rendering foo:initial_foo bar:initial_bar
rendering foo:updated_foo bar:updated_bar
The demo (在演示控制台中的输出是重复的,因为它开启了 React 严格模式)更新:
问题已在 React project repo 中创建
最佳答案
在 promise 中(以及与此相关的计时器)setState
是同步的。因此,您有两个重新渲染(首先是 setFoo
,然后是 setBar
),而不是组合(批处理)一个。
这是一个例子:
如果你点击 Promise 按钮中的 setState,你会得到 rerender
在每个状态之后记录,但是如果您在 eventHandler 中单击 setState,您将首先获得两个 setState 日志,然后只有一个重新渲染。这种行为应该在 React 17 中改变,其中状态更新只会是异步的。
function App() {
const [foo, setFoo] = React.useState();
const [bar, setBar] = React.useState();
function handlerPromise() {
Promise.resolve().then(() => {
console.log("setFoo");
setFoo({});
console.log("setBar");
setBar({});
})
}
function handler() {
console.log("setFoo");
setFoo({});
console.log("setBar");
setBar({});
}
console.log("rerender");
return (
<div>
<button onClick={handlerPromise}>setState in Promise</button>
<button onClick={handler}>setState in an eventHandler</button>
</div>
)
}
ReactDOM.render(<App/>, document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.development.js"></script>
<div id="root"></div>
关于javascript - 为什么每个状态钩子(Hook)更新都会导致异步代码中的单独重新渲染?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63678705/