背景
在开发一个需求时,需要对原来的 form 表单组件代码复用并进行拓展。
场景A 使用原来的 form 表单组件。
场景B 在原来的表单组件基础上,新增一些表单项,新增表单项位置动态插入在原来的表单组件中,位置随意。
需求
复用表单组件,同时支持新增表单项。
解决方案
在 React 中,组件扩展和定制的能力,可以通过 props.children
和 render props 来实现。
以上两种方式的缺点是:如果插入位置比较分散,需要定义children对象或多个 props,代码繁琐,不易维护。调研下来,目前貌似没其他好的方法... 欢迎补充
props.children
props.children 直接将内容作为一个HTML内嵌结构编写,将组件参数与内嵌结构分开写。
children 可以是一个字符串, 数组,对象等类型。可以使用 React.Children 的方法来判断props.children 类型并处理。
function Father() { return ( <div> 我是父组件Father <Form1> <div>我是子组件Form1的children</div> </Form1> <Form2> {{ title: (<div>我是子组件Form2的title</div>), content: (<div>我是子组件Form2的content</div>) }} </Form2> </div> ) } function Form1(props) { return ( <div> 我是子组件Form1 {props.children} </div> ) } function Form2(props) { return ( <div> 我是子组件Form2 {props.children.title} {props.children.content} </div> ) }
render props
通过 props
参数传入 JSX 元素的方法渲染,告知组件需要渲染什么内容的函数 prop。可以定义多个 props 参数,不同位置渲染不同的 props。
function Father() { return ( <div> 我是父组件Father <Form1 children={<div>我是子组件Form1的children</div>} /> <Form2 title={<div>我是子组件Form2的title</div>} content={<div>我是子组件Form2的content</div>} /> </div> ) } function Form1(props) { return ( <div> 我是子组件Form1 {props.children} </div> ) } function Form2(props) { return ( <div> 我是子组件Form2 {props.title} {props.content} </div> ) }
dataset
React 没有专门的插槽,根据 children/props
的特性,加上只读属性 dataset
实现一个类似的插槽功能。
非必要不使用,代码会更加繁琐。
如果有条件判断是否展示,可以灵活设置 dataset 值使用。
function Father() { return ( <div> 我是父组件Father <Form1 type='text1' title={<div>我是子组件Form的title</div>} bottom={<div>我是子组件Form的bottom</div>} > <div data-type='text1'> <label>性别:</label> <input type="text" name="gender" /> </div> <div data-type='text1,text2'> <label>身高:</label> <input type="text" name="height" /> </div> <div data-type='text2,text3'> <label>体重:</label> <input type="text" name="weight" /> </div> </Form1> </div> ) } function Form1({ title, bottom, type, children, }) { const renderChild = useMemo(() => { return children .filter(itm => itm.props['data-type']?.split(',')?.includes(type)) .map(itm => itm.props.children) }, [type, children]) return ( <div> 我是子组件Form1 {title} <div> <label>姓名:</label> <input type="text" name="name" /> </div> {renderChild} <div> <label>年龄:</label> <input type="number" name="age" /> </div> {bottom} </div> ) }