React:条件组件使用render props触发hooks错误如何解决?
问题背景
使用Render Prop模式开发React组件时,点击按钮触发组件渲染后出现错误:Rendered more hooks than during the previous render.。但改用JSX直接写法({isOpen ? <Component num={123} /> : null})时不会触发该错误。
复现代码
import React, { useState } from 'react'; function Component({ num }) { const [lorem, setLorem] = useState('lorem'); return ( <div> {num}: {lorem} </div> ); } function AnotherComponent({ render = Component }) { const [isOpen, setIsOpen] = useState(false); return ( <div> <button type="button" onClick={() => setIsOpen(true)}> Open </button> {isOpen ? render({ num: 123 }) : null} </div> ); }
错误根源
直接调用render({ num: 123 })时,Component函数会被当作普通函数在AnotherComponent的渲染流程中执行,Component内的useState会被计入AnotherComponent的Hook数量。首次渲染时isOpen为false,该函数未执行,AnotherComponent仅包含1个Hook;点击按钮后函数执行,AnotherComponent的Hook数量变为2个,违反了React Hook「每次渲染时Hook的调用顺序和数量必须一致」的核心规则。
而JSX写法中,Component是作为独立子组件存在,它的Hook属于自身组件实例,AnotherComponent的Hook数量始终固定,因此不会触发错误。
解决方案
方案1:将render prop的函数调用改为JSX语法
这是最直接的解决方式,把函数调用改成JSX写法,让React将其视为独立组件,隔离Hook的执行上下文:
function AnotherComponent({ render = Component }) { const [isOpen, setIsOpen] = useState(false); return ( <div> <button type="button" onClick={() => setIsOpen(true)}> Open </button> {isOpen ? <render num={123} /> : null} </div> ); }
方案2:用中间组件包裹render prop调用
如果无法使用JSX写法,可以封装一个中间组件来隔离父组件与render prop的Hook执行:
// 封装用于隔离的中间组件 function RenderPropWrapper({ render, props }) { return render(props); } // 修改后的AnotherComponent function AnotherComponent({ render = Component }) { const [isOpen, setIsOpen] = useState(false); return ( <div> <button type="button" onClick={() => setIsOpen(true)}> Open </button> {isOpen ? <RenderPropWrapper render={render} props={{ num: 123 }} /> : null} </div> ); }
中间组件RenderPropWrapper仅在isOpen为true时挂载,它的渲染流程会执行render函数的Hook,不会影响父组件AnotherComponent的Hook数量计数。
内容的提问来源于stack exchange,提问作者rwacarter

