React中无需setTimeout实现状态更新触发重渲染的方法
React状态更新不触发重渲染?别用setTimeout,这么改才对
我在开发React应用时遇到了一个问题:操作数组内的对象后,状态明明更新了,但组件却不触发重渲染。试了在状态更新前加setTimeout居然解决了问题,但代码里堆了一堆setTimeout肯定不是最优解,想请教怎么不用它实现需求?
原问题代码
点击切换数组内对象active状态的函数:
const documentCopy = selectedDocument; documentCopy.pages.find((page) => page.id === id ? page.active === true ? (page.active = false) : (page.active = true) : null, ); setTimeout(() => { setSelectedDocument(null); setSelectedDocument(documentCopy); }, 1);
渲染部分代码:
{selectedDocument && selectedDocument.pages.map((page, i) => ( <IconButton key={page.id} onClick={() => handleCheckImage(page.id)} > <img src={page.img} width='85' height='120' alt={'document'} /> <Checkbox className={ page.active ? classes.imageBox : classes.imageBoxHidden } checked={page.active} name={page.id} color='primary' /> </IconButton> ) )}
需求很简单:点击图片后,对应图片的复选框能实时切换选中/未选中状态。
问题根源
原代码里const documentCopy = selectedDocument;只是把状态对象的引用赋值给了新变量,修改documentCopy.pages本质是在直接修改原状态的引用类型数据。React判断状态是否变化靠的是引用对比,你把同一个引用传给setSelectedDocument,React会认为状态没变化,自然不会触发重渲染。
而setTimeout里先设null再设回原对象,相当于强制让状态经历了"有→无→有"的变化才触发重渲染,这只是临时的hack,完全没必要。
正确解法:创建新的状态副本
要让React检测到状态变化,必须创建新的对象/数组副本,确保状态的引用发生改变。更规范的写法如下:
// 复制整个文档对象,避免直接修改原状态 const updatedDocument = { ...selectedDocument }; // 遍历pages数组,只修改目标page的active状态,其余保持原样 updatedDocument.pages = updatedDocument.pages.map(page => { if (page.id === id) { return { ...page, active: !page.active }; } return page; }); // 传入新对象触发重渲染 setSelectedDocument(updatedDocument);
参考建议后修改的最终代码
const docPages = selectedDocument.pages; docPages.find((page) => page.id === id ? page.active === true ? (page.active = false) : (page.active = true) : null, ); setSelectedDocument({ ...selectedDocument, pages: docPages });
这里通过{ ...selectedDocument }创建了新的文档对象,即使pages数组是原引用,外层对象的新引用也能让React检测到状态变化,从而触发重渲染。不过更严谨的写法是连目标page也一起复制(即上面的map写法),完全遵循React状态不可变的原则。
内容的提问来源于stack exchange,提问作者André Sartoreto
相关产品推荐
相关产品推荐

