React useState无限循环或不更新问题求助
问题背景
我在使用React的useState时遇到异常行为,希望基于依赖前状态的计算结果控制子元素的显示与隐藏。
问题1:直接执行状态更新导致无限重渲染
组件代码如下,直接在组件函数体内调用setPositionsVorschlagVisible会触发无限循环:
export function PersonWidget(props: Props) { const store = useTableState(); const [positionsVorschlagVisible, setPositionsVorschlagVisible] = React.useState(false); var positionsVorschlag = calculatePositionsvorschlag( convertTokenToDTOToken(store.previousTokens), convertTokenToDTOToken(store.tokens), props.tokenId, positionsVorschlagVisible.valueOf() ); // 这段代码导致无限循环 setPositionsVorschlagVisible(visible => (positionsVorschlagVisible && positionsVorschlag.showAction === showAction.keep) || positionsVorschlag.showAction === showAction.show); return ( <div style={styles}> {positionsVorschlagVisible && <PositionsVorschlag /> } <Rente /> </div> ) }
问题2:放入useEffect后初始渲染后不再触发
尝试将状态设置逻辑移入useEffect,但初始渲染后该逻辑不再执行:
// 初始渲染后不再触发 useEffect(() => { setPositionsVorschlagVisible(visible => (positionsVorschlagVisible && positionsVorschlag.showAction === showAction.keep) || positionsVorschlag.showAction === showAction.show); }, [positionsVorschlagVisible]);
补充:父组件信息
父组件是依赖Mobx store变更渲染的observer组件:
export const Screen = observer((): React.ReactElement => { const store = useTableState(); var personTokens = store.tokens.filter(token => token.type === 'person'); return (<div style={ScreenStyles}> {personTokens.map((personToken => { return ( <div key={personToken.id} style={{ position: 'absolute', top: personToken.center.y, left: personToken.center.x }}> <PersonWidget tokenId={personToken.id}/> </div> ) }))} </div>) });
疑问:为何positionsVorschlagVisible状态变更后组件未重渲染?是否错误使用了useEffect或useState?
问题分析与解决方案
1. 无限重渲染的原因
直接在组件函数体内调用setPositionsVorschlagVisible,每次组件渲染时都会执行状态更新,状态更新又会触发组件重新渲染,形成无限循环。
2. useEffect失效的原因
你的useEffect依赖数组只加了positionsVorschlagVisible,但positionsVorschlag的计算依赖store.previousTokens、store.tokens和props.tokenId,这些值变化时并不会触发useEffect执行。另外,useEffect的回调里使用了外部的positionsVorschlagVisible和positionsVorschlag,但这些值在useEffect闭包中是固定的(初始渲染时的值),即使后续这些值变化,闭包也不会更新。
正确的实现方式
export function PersonWidget(props: Props) { const store = useTableState(); const [positionsVorschlagVisible, setPositionsVorschlagVisible] = React.useState(false); // 用useMemo缓存计算结果,避免每次渲染重复计算 const positionsVorschlag = React.useMemo(() => { return calculatePositionsvorschlag( convertTokenToDTOToken(store.previousTokens), convertTokenToDTOToken(store.tokens), props.tokenId, positionsVorschlagVisible ); }, [store.previousTokens, store.tokens, props.tokenId, positionsVorschlagVisible]); // 监听计算结果变化,触发状态更新 React.useEffect(() => { setPositionsVorschlagVisible(prevVisible => (prevVisible && positionsVorschlag.showAction === showAction.keep) || positionsVorschlag.showAction === showAction.show ); }, [positionsVorschlag]); return ( <div style={styles}> {positionsVorschlagVisible && <PositionsVorschlag />} <Rente /> </div> ) }
关键点说明:
- 用
useMemo缓存positionsVorschlag的计算结果,只有当依赖项变化时才重新计算,提升性能。 - useEffect的依赖数组改为
positionsVorschlag,确保每次计算结果变化时都触发状态更新。 - 在
setPositionsVorschlagVisible的回调中使用prevVisible参数(而不是外部的positionsVorschlagVisible),这样能获取到最新的前状态,避免闭包问题。
额外注意:Mobx与React的配合
父组件是Mobx的observer,当store中的tokens或previousTokens变化时,父组件会重新渲染,进而触发PersonWidget的重新渲染。确保useTableState返回的store是被Mobx正确观察的对象,这样store的变化能及时传递到子组件。
内容的提问来源于stack exchange,提问作者Yves1982

