React中可调用父组件回调修改提升状态的位置咨询
状态提升是React中组件间共享状态的常见方案,通常父组件会将状态和对应的更新回调传递给子组件,让子组件可以通知父组件修改状态。但官方文档中对子组件调用该回调的具体合法位置没有集中说明,下面结合官方规则、文档示例,对伪代码中标记的5个位置逐一分析:
function Parent({}){ const [isActive, setIsActive] = useState(false); return ( <> <Foo/> <Child active={isActive} onChange={setIsActive}/> </> ); } function Child({active,onChange}){ // Location 1: in the render code onChange(old => !old); useEffect(() => { // Location 2: in a useEffect onChange(old => !old); // Location 3: on complete of some async operation in the useEffect asyncOperationLikeFetch().then( () => onChange(old => !old)); }, []) return ( <> <p>{active}</p> <button onClick={() => { // Location 4: in an event handler onChange(old => !old) // Location 5: on complete of some async operation in the event handler asyncOperationLikeFetch().then( () => onChange(old => !old)); }}>Toggle</button> </> ); }
各位置合法性分析
位置1:渲染代码中:不允许
React官方文档明确:「此外,你只能以此方式更新当前正在渲染的组件的状态。在渲染期间调用另一个组件的set函数是错误的」
渲染阶段的代码要求是纯函数,不能产生副作用。调用父组件的状态更新回调会触发父组件重新渲染,进而导致子组件再次渲染,极易引发无限循环,违反React的渲染规则。位置2:useEffect中:允许
useEffect的核心作用就是处理渲染阶段之外的副作用,包括数据获取、订阅、状态更新等操作。React官方在《Using the Effect Hook》章节明确说明,Effect用于执行那些不适合在渲染时进行的逻辑,调用父组件的状态更新回调属于典型的副作用操作,完全符合useEffect的设计场景。位置3:useEffect中异步操作完成后:允许
异步操作完成后的逻辑属于Effect副作用的延伸,React对Effect内部的异步任务没有限制。只要在组件卸载时做好清理(比如用AbortController取消未完成的请求),避免内存泄漏,这种调用方式完全合法。官方在数据获取的示例中,就有在Effect的异步回调中更新状态的场景,可直接推导出调用父组件回调的合法性。位置4:事件处理器中:允许
这是React组件间通信的标准模式,官方文档中有大量示例(如表单组件、Toggle开关)都采用这种方式。事件处理器属于用户交互触发的非渲染阶段逻辑,调用父组件回调触发状态更新完全符合官方设计规范。位置5:事件处理器中异步操作完成后:允许
这是业务开发中的常见场景(比如点击按钮发起请求,请求完成后更新状态)。React对事件处理器内部的异步逻辑没有限制,只要保证操作符合预期(比如避免重复请求),这种调用方式就是合法的。官方在事件处理和数据获取的相关示例中,也认可这种异步触发状态更新的模式,同理适用于调用父组件的回调。
内容的提问来源于stack exchange,提问作者Robin

