javascript - 为什么每个状态钩子更新都会导致异步代码中的单独重新渲染?
问题描述
一个组件中使用了两个useState
钩子。如果我们允许事件循环在修改状态之前“重排”,每个钩子都会导致单独的重新渲染。
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
演示 (在演示控制台输出重复,因为它启用了 React 严格模式)
- 为什么会这样?
- 它是错误还是功能?
- 我们如何防止这种中间重新渲染?
更新:
问题已在React 项目 repo中创建
解决方案
在 Promise 中(以及与此相关的计时器)setState
是同步的。因此,您有两个重新渲染(首先是 for setFoo
,然后是 for setBar
),而不是组合(批处理)一个。
这是一个例子:
如果您在 Promise按钮中单击setState,您将在每个状态之后记录,但如果您在 eventHandler 中rerender
单击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>
推荐阅读
- r - 如何将闪亮的应用图像下载到 Rmarkdown pdf 中?
- listview - 如何使用标题和 ListTile 制作列表视图?
- python - Python 3.7 和 3.4 GPS3 模块上的 WinError10057
- python - if else 语句中使用的递归
- android - Android Material TextInputLayout 错误信息显示在 Material TextInputEditText child 后面
- python - 列表的条件位置比较基于
- linux - 将批处理文件函数转换为 bash
- ruby - Vagrant 在一台机器上将 WSL 视为 Linux(在其他地方将 WSL 视为 Windows)
- c# - 如何在子更新上修改父实体?(一对多)
- python - 给定一个字典迭代器,得到字典