React中是否有接收特定子组件的惯用实现方式?
问题描述
我需要实现一个Card组件,用来承载页眉、主体和页脚内容。目前有两种调用思路:
第一种是通过props直接传递内容:
<Card header="foo" body="bar" footer="baz" />
但我更偏好嵌套组件的写法,结构更清晰:
<Card> <CardHeader>foo</CardHeader> <CardBody>bar</CardBody> <CardFooter>baz</CardFooter> </Card>
我现在是这么实现嵌套写法的:
const cardHeader = children.find( (child) => child && child.type && child.type.name === "CardHeader", );
想问问有没有更符合React惯用写法的实现方式?
惯用实现方式
1. 直接对比组件类型(避免依赖name属性)
依赖child.type.name有个坑:如果项目开启代码压缩,组件的name属性会被混淆成随机字符,导致判断失效。更可靠的方式是直接对比组件本身:
import CardHeader from './CardHeader'; import CardBody from './CardBody'; import CardFooter from './CardFooter'; function Card({ children }) { let header, body, footer; React.Children.forEach(children, child => { // 先判断是否是有效的React元素 if (React.isValidElement(child)) { if (child.type === CardHeader) { header = child; } else if (child.type === CardBody) { body = child; } else if (child.type === CardFooter) { footer = child; } } }); return ( <div className="card"> {header} {body} {footer} </div> ); }
这种写法完全规避了压缩带来的问题,判断逻辑也更直接。
2. 用静态属性标记插槽类型
可以给每个子组件添加一个静态属性来标识它对应的插槽,后续新增插槽时扩展性更强:
// CardHeader.jsx function CardHeader({ children }) { return <div className="card-header">{children}</div>; } // 添加静态属性标记插槽类型 CardHeader.slot = 'header'; // 同理给CardBody和CardFooter添加对应的slot属性 // CardBody.slot = 'body' // CardFooter.slot = 'footer' function Card({ children }) { const slots = {}; React.Children.forEach(children, child => { if (React.isValidElement(child) && child.type.slot) { slots[child.type.slot] = child; } }); return ( <div className="card"> {slots.header} {slots.body} {slots.footer} </div> ); }
用户调用时的写法完全不变,但内部逻辑更灵活,就算以后加个CardExtra插槽,只需要给它加个slot='extra'就行。
3. React Context实现插槽注册(适合复杂场景)
如果你的Card组件需要支持多层嵌套、动态插槽这类复杂需求,可以用Context让子组件自动注册到父组件:
const CardContext = React.createContext(null); function Card({ children }) { const [slots, setSlots] = React.useState({}); // 注册插槽的方法,传给子组件 const registerSlot = (type, element) => { setSlots(prev => ({ ...prev, [type]: element })); }; return ( <CardContext.Provider value={registerSlot}> <div className="card"> {slots.header} {slots.body} {slots.footer} </div> </CardContext.Provider> ); } // CardHeader.jsx function CardHeader({ children }) { const registerSlot = React.useContext(CardContext); React.useEffect(() => { // 把当前组件的内容注册到父组件的header插槽 registerSlot('header', <div className="card-header">{children}</div>); }, [registerSlot, children]); return null; // 这里返回null,因为内容已经通过Context传递给父组件渲染了 }
这种写法适合复杂场景,不过简单的Card组件用前两种方法就足够了,没必要搞这么复杂。
内容的提问来源于stack exchange,提问作者Adam Zerner
相关产品推荐
相关产品推荐

