useState引发无限渲染:两种写法为何结果截然不同?
问题场景
几行React代码触发了无限渲染错误,对比两种写法的运行结果差异明显:
错误代码(触发无限渲染)
function FarmerComponent(props) { let authCtx = useContext(AuthContext) let usersAndItemsCtx = useContext(usersAndProductsContext) let current_logged_user = usersAndItemsCtx.usersVal.find(user => user.id === authCtx.currentLoggedUserId); let [isCurrentFarmerLikedFlag, isCurrentFarmerLikedFlagToggle] = useState(false) console.log(current_logged_user.likedProfilesId) let flag = (current_logged_user.likedProfilesId.find((id) =>id === props.id) ? true : false) isCurrentFarmerLikedFlagToggle(flag); console.log(flag) }
触发的错误信息
react-dom.development.js:16317 Uncaught Error: Too many re-renders.
React limits the number of renders to prevent an infinite loop.
at renderWithHooks (react-dom.development.js:16317:1)
at updateFunctionComponent (react-dom.development.js:19588:1)
at beginWork (react-dom.development.js:21601:1)
at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1)
at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1)
at invokeGuardedCallback (react-dom.development.js:4277:1)
at beginWork$1 (react-dom.development.js:27451:1)
at performUnitOfWork (react-dom.development.js:26557:1)
at workLoopSync (react-dom.development.js:26466:1)
at renderRootSync (react-dom.development.js:26434:1)
控制台会多次打印false,说明组件一直在重复渲染。
正确代码(无无限渲染)
function FarmerComponent(props) { let authCtx = useContext(AuthContext) let usersAndItemsCtx = useContext(usersAndProductsContext) let current_logged_user = usersAndItemsCtx.usersVal.find(user => user.id === authCtx.currentLoggedUserId); console.log(current_logged_user.likedProfilesId) let flag = (current_logged_user.likedProfilesId.find((id) =>id === props.id) ? true : false) console.log(flag) let [isCurrentFarmerLikedFlag, isCurrentFarmerLikedFlagToggle] = useState(flag) }
本质差异与错误原因
- 错误写法的核心问题:在组件的顶层渲染逻辑中直接调用状态更新函数
isCurrentFarmerLikedFlagToggle(flag)。React的运行规则是:任何状态更新操作都会触发组件重新渲染。这就形成了无限循环:- 组件首次渲染 → 调用状态更新函数 → 触发重新渲染
- 组件重新渲染 → 再次执行顶层代码,调用状态更新函数 → 再次触发渲染
循环持续到React触发保护机制,抛出过多渲染的错误。哪怕每次flag的值都是false,只要调用了状态更新函数,React就会触发渲染流程。
- 正确写法的逻辑:把计算好的
flag作为useState的初始参数传入,状态只会在组件第一次挂载时初始化。后续除非主动调用isCurrentFarmerLikedFlagToggle(比如在事件处理函数中),否则不会触发额外的渲染,自然不会出现循环。
如果后续需要根据current_logged_user或props.id的变化动态更新状态,应该用useEffect监听依赖项,在依赖变化时再更新状态,示例:
// 动态更新状态的正确方式 useEffect(() => { const flag = current_logged_user.likedProfilesId.some(id => id === props.id); isCurrentFarmerLikedFlagToggle(flag); }, [current_logged_user.likedProfilesId, props.id]);
(这里用some替代find更符合判断存在性的语义,返回值本身就是布尔值,不需要额外的三元表达式)
内容的提问来源于stack exchange,提问作者dani_l_n

