Next.js 13 RSC中如何识别React懒加载组件的类型?
问题
我正在使用Next.js 13和React Server Components,写了一个Block组件作为BlockHeading、BlockBody这类子组件的父组件,处理逻辑如下:
export function Block({ children }) { const [visible, setVisible] = useState(true); return ( <div className="m-5"> {React.Children.map(children, (child) => { if (!React.isValidElement(child)) { console.error("invalid Block child", child); return null; } switch (child.type) { case BlockHeading: return React.cloneElement(child, { visible, onClick: () => setVisible(!visible) }); case BlockBody: return React.cloneElement(child, { visible }); default: console.error("invalid Block child", child); return; } })} </div> ); }
但在React Server Component中使用Block时,子组件的child.type变成了带Symbol(react.lazy)的对象:
{ $$typeof: Symbol(react.lazy), type: { ... }, ... }
推测是Next.js/React自动把组件转成了懒加载形式,现在想知道怎么在child.type不保留原组件类型的情况下,维持原来基于子组件类型的switch判断逻辑?
解决方案
方法1:给子组件添加静态标识属性
给BlockHeading、BlockBody这类子组件添加专属静态标识,绕过懒加载包装的影响来判断类型。
先修改子组件:
export function BlockHeading(props) { // 组件原有逻辑 } // 添加静态标识属性 BlockHeading.componentType = 'BlockHeading'; export function BlockBody(props) { // 组件原有逻辑 } BlockBody.componentType = 'BlockBody';
再修改Block组件的判断逻辑,增加懒加载组件的解析逻辑:
export function Block({ children }) { const [visible, setVisible] = useState(true); // 解析懒加载包装后的组件真实类型 const getComponentType = (element) => { let component = element.type; // 逐层解开react.lazy的包装 while (component?.$$typeof === Symbol.for('react.lazy')) { component = component._payload?.result ?? component.type; } return component?.componentType; }; return ( <div className="m-5"> {React.Children.map(children, (child) => { if (!React.isValidElement(child)) { console.error("invalid Block child", child); return null; } const componentType = getComponentType(child); switch (componentType) { case 'BlockHeading': return React.cloneElement(child, { visible, onClick: () => setVisible(!visible) }); case 'BlockBody': return React.cloneElement(child, { visible }); default: console.error("invalid Block child", child); return; } })} </div> ); }
方法2:通过自定义props区分
在使用子组件时,给每个子组件添加自定义blockRole属性,父组件直接读取该属性判断类型,无需修改子组件源码。
使用示例:
<Block> <BlockHeading blockRole="heading">折叠标题</BlockHeading> <BlockBody blockRole="body">折叠内容区域</BlockBody> </Block>
修改Block组件的判断逻辑:
export function Block({ children }) { const [visible, setVisible] = useState(true); return ( <div className="m-5"> {React.Children.map(children, (child) => { if (!React.isValidElement(child)) { console.error("invalid Block child", child); return null; } const { blockRole } = child.props; switch (blockRole) { case 'heading': return React.cloneElement(child, { visible, onClick: () => setVisible(!visible) }); case 'body': return React.cloneElement(child, { visible }); default: console.error("invalid Block child", child); return; } })} </div> ); }
方法3:利用组件displayName判断
如果子组件已经设置了displayName,可以通过解析懒加载组件的displayName来判断,但要注意displayName可能存在重复风险,可靠性略低于自定义标识。
先给子组件设置displayName:
export function BlockHeading(props) { /* 组件逻辑 */ } BlockHeading.displayName = 'BlockHeading'; export function BlockBody(props) { /* 组件逻辑 */ } BlockBody.displayName = 'BlockBody';
修改Block组件的解析逻辑:
export function Block({ children }) { const [visible, setVisible] = useState(true); const getDisplayName = (element) => { let component = element.type; while (component?.$$typeof === Symbol.for('react.lazy')) { component = component._payload?.result ?? component.type; } return component?.displayName || component?.name; }; return ( <div className="m-5"> {React.Children.map(children, (child) => { if (!React.isValidElement(child)) { console.error("invalid Block child", child); return null; } const displayName = getDisplayName(child); switch (displayName) { case 'BlockHeading': return React.cloneElement(child, { visible, onClick: () => setVisible(!visible) }); case 'BlockBody': return React.cloneElement(child, { visible }); default: console.error("invalid Block child", child); return; } })} </div> ); }
内容的提问来源于stack exchange,提问作者sdgluck
相关产品推荐
相关产品推荐

