前言
最近开始学习React,跟着Kent学,有很多干货,这里分享Compound Components组合组件
文中完整的示例代码可以查看 这里
一、background
1.1 example
组合模式是backend的一个概念,但是frontend其实也是有类似的设计模式的,比如select跟options的组合
<select>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
</select>
1.2 需求
为什么需要这种组合的模式呢,其实不用组合模式也能实现
<CustomSelect
options={[
{value: '1', display: 'Option 1'},
{value: '2', display: 'Option 2'},
]}
/>
主要是因为扩展性,可以看出来使用了组合模式select以及option的分工明确,select更像是个controller控制整个组件,而option则是给了select更多控制的方法
二、React的组合模式
2.1 需求
需要设计如下组件, ToggleOn, ToggleOff 以及ToggleButton不需要用户传props属性,这些属性在创建Toggle时候自动pass过去了
const ToggleOn = ({on, children}) => on ? children : null
const ToggleOff = ({on, children}) => on ? null : children
const ToggleButton = ({on, toggle}) => <Switch on={on} onClick={toggle}/>
<Toggle>
<ToggleOn>The button is on</ToggleOn>
<ToggleOff>The button is off</ToggleOff>
<ToggleButton />
</Toggle>
2.2 设计
根据需求,Toggle需要给子组件传递on, toggle的属性,这样子组件才能使用
不妨先把Toggle组件设计如下
function Toggle() {
const [on, setOn] = React.useState(false)
const toggle = () => setOn(!on)
return <Switch on={on} onClick={toggle} />
}
<ToggleOn on={on}>The button is on</ToggleOn>
<ToggleOff off={off}>The button is off</ToggleOff>
<ToggleButton toggle={toggle}/>
这个设计有个问题,就是Toggle确实有了on以及toggle的属性,但是这些属性需要触传递给子组件,背离了设计的初衷
需要组件自动就获得父组件的属性,不需要传递props
2.3 新的设计
既然不需要传递props,那么就需要修改子组件的props
不妨把Toggle组件修改如下
function Toggle(props) {
const [on, setOn] = React.useState(false)
const toggle = () => setOn(!on)
const childrenWithProps = React.Children.map(props.children, child => {
child.props.on = on
child.props.toggle = toggle
})
return childrenWithProps
}
Note: React.children.map可以拿到父组件下的所有子组件
但是会报错,因为React本身是不支持修改child的props的,这也符合React的设计理念,immutable;如果要改变child的props不应修改原来的child而是直接返回新的child
这里需要使用到React下的一个API - cloneelement
使用方法就是
//这样就能获得新的element
React.cloneElement(
element,
[props],
[...children]
)
于是,新的Toggle组件修改如下
function Toggle(props) {
const [on, setOn] = React.useState(false)
const toggle = () => setOn(!on)
const childrenWithProps = React.Children.map(props.children, child => {
return React.cloneElement(
child,
{on, toggle}
)
})
console.log('elements ', childrenWithProps)
return childrenWithProps
}
于是子组件就有了on以及toggle的属性,满足了设计需求,
2.4 支持DOM元素
对于DOM元素,比如div,是不支持React.cloneElement的,所以需要过滤掉,做法也很简单,就是判断一下child.type
const ChildrenWithProps = React.Children.map(props.children, child => {
if( typeof child.type === 'string') return child
return React.cloneElement(
child,
{on, toggle}
)
})
总结
组合组件模式通过把props直接复制给新的child,减少了props的传递,在一定程度上可以跟context替换