如何正确识别并提取React组件中指定类型的子元素?
解决方案
实现思路
最优方案是为子组件添加唯一的静态标识,遍历props.children时通过该标识匹配对应组件,相比判断key、displayName的方式更稳定,完全支持任意子组件组合、任意传入顺序的需求。
完整修改后代码
import React, { FunctionComponent, isValidElement, Children } from 'react'; import { FormControl, FormGroup, FormGroupProps, FormLabel, FormText, FormTextProps } from 'react-bootstrap'; import { FormLabelOwnProps } from 'react-bootstrap/esm/FormLabel'; // 定义唯一标识,用Symbol可完全避免命名冲突 const TEXT_INPUT_LABEL = Symbol('TextInputLabel'); const TEXT_INPUT_NOTE = Symbol('TextInputNote'); interface TextInputProps { className?: string, value?: string, placeholder?: string, disabled?: boolean, containerProps?: FormGroupProps onChange: (value: string) => void children?: React.ReactNode } const TextInput: FunctionComponent<TextInputProps> & { Label: FunctionComponent<LabelProps>, Note: FunctionComponent<NoteProps> } = (props) => { let labelNode = null; let noteNode = null; // 遍历子元素匹配对应组件 Children.forEach(props.children, child => { if (!isValidElement(child)) return; const componentType = (child.type as any).__COMPONENT_TYPE__; if (componentType === TEXT_INPUT_LABEL) { labelNode = child; } else if (componentType === TEXT_INPUT_NOTE) { noteNode = child; } }) return ( <FormGroup {...props.containerProps} > {/* 渲染匹配到的Label */} {labelNode} <FormControl className={props.className} disabled={props.disabled} value={props.value} placeholder={props.placeholder} as='input' type='text' onChange={(e) => props.onChange(e.target.value)} /> {/* 渲染匹配到的Note */} {noteNode} </FormGroup> ); } // Input label interface LabelProps extends FormLabelOwnProps { text: string } const Label: FunctionComponent<LabelProps> & { __COMPONENT_TYPE__: symbol } = (props: LabelProps) => { return ( <FormLabel {...props} >{props.text}</FormLabel> ); } // 给Label添加静态标识 Label.__COMPONENT_TYPE__ = TEXT_INPUT_LABEL; TextInput.Label = Label; // Input note interface NoteProps extends FormTextProps { text: string } const Note: FunctionComponent<NoteProps> & { __COMPONENT_TYPE__: symbol } = (props: NoteProps) => { return ( <FormText {...props} >{props.text}</FormText> ); } // 给Note添加静态标识 Note.__COMPONENT_TYPE__ = TEXT_INPUT_NOTE; TextInput.Note = Note; export default TextInput;
用法示例
支持任意组合的传入方式,子组件传入顺序不影响最终渲染位置:
{/* 仅传Label */} <TextInput onChange={v => console.log(v)}> <TextInput.Label text="用户名" /> </TextInput> {/* 同时传两个,顺序随意 */} <TextInput onChange={v => console.log(v)}> <TextInput.Note text="请输入6-12位字符" /> <TextInput.Label text="用户名" /> </TextInput>
方案优势
- 全场景适配:完全满足不传、仅传单个组件、两个都传的需求
- 顺序无关:无需约束用户传入子组件的顺序
- 稳定性高:用Symbol作为唯一标识,不会受打包压缩、组件代理包裹等场景影响出现判断失效
- 类型安全:完全适配TypeScript类型校验规则
内容的提问来源于stack exchange,提问作者Tristan
相关产品推荐
相关产品推荐

