React中的设计模式 - 组合组件(上)

前言

最近开始学习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替换

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值