Advanced-react-patterns(2)

本文介绍了如何利用 React 中的 Static 特性简化组件间的 props 传递。通过 Toggle 组件的具体案例,展示了如何使用静态属性关联组件,以及 React.cloneElement 的应用,减少子组件间相同 props 的重复传递。

文本是 ADVANCED-REACT-PATTERNS 第二篇。

Static 妙用

function App() {
  return (
    <Toggle
      onToggle={on => console.log('toggle', on)}
    >
      <Toggle.On>The button is on</Toggle.On>
      <Toggle.Off>The button is off</Toggle.Off>
      <Toggle.Button />
    </Toggle>
  )
}
复制代码

首先外层是一个 Toggle组件,包括着内部的子组件。因为外层组件包裹的命名比较清晰。

注意这中间有不太常见的组件使用方式。

<Toggle.On>
复制代码

Toggle组件是一个 component 类, On 是它的属性,不过既然作为组件使用,说明也是一个 component 类。 继续研究下 Toggle 组件的实现,去寻找答案。

class Toggle extends React.Component {
  static On = ToggleOn
  static Off = ToggleOff
  static Button = ToggleButton
  static defaultProps = {onToggle: () => {}}
  state = {on: false}
  toggle = () =>
    this.setState(
      ({on}) => ({on: !on}),
      () => this.props.onToggle(this.state.on),
    )
  render() {
    const children = React.Children.map(
      this.props.children,
      child =>
        React.cloneElement(child, {
          on: this.state.on,
          toggle: this.toggle,
        }),
    )
    return <div>{children}</div>
  }
}

复制代码

在实际 debug 的时候组件渲染民时最终还是被替换成 <ToggleOn> 以及 <ToggleOff>

static 是 ES6 中 Class 定义静态方法的关键字。静态方法的好处是在基于类创建新的实例时,新的实例是不会继承静态方法的。(简单来说,不是在原型链上定义的,而是直接挂载在这个类的属性中)

这么一看 Static 里面的静态属性引入了外部定义的类(如 ToggleOn)

function ToggleOn({on, children}) {
  return on ? children : null
}
function ToggleOff({on, children}) {
  return on ? null : children
}
function ToggleButton({on, toggle, ...props}) {
  return (
    <Switch on={on} onClick={toggle} {...props} />
  )
}
复制代码

通过 Static 关联组件,是有原因的。

使用 React.cloneElement 减少子组件相同props传递

<Toggle.On> 等类似的组件中,我们发现 render 中并没有直接传入 props ,而 ToggleOn 组件就是接受了props,第一个对象为 on, 第二个则是包裹的 children。

代码产生的实际效果应该是这样的。

render() {
	<Toggle.On on={this.state.on}>The button is on</Toggle.On>
}
复制代码

我们再把思路回溯到 Toggle 的 render 这一方法。

render() {
  const children = React.Children.map(
    this.props.children,
    child =>
      React.cloneElement(child, {
        on: this.state.on,
        toggle: this.toggle,
      }),
  )
  return <div>{children}</div>
}
复制代码
React.cloneElement

React.cloneElement 接受一个组件,以及可选的props以及children。它的作用就是 clone 一份原先组件并将其替换成新组件。原先组件的 props 会被新传入的 props 浅合并。而新的children 会替换成原来的 children, 不过组件原先的 key 和 ref 会得到保留。

React.cloneElement(
  element,
  [props],
  [...children]
)
复制代码

它的实际作用类似于

<element.type {...element.props} {...props}>{children}</element.type>
复制代码

由于 this.props.chidlren 的数据类型是不确定的,可能是个 Object,也可能是 Array.官方文档推荐使用 React.Chidlren 为 children 提供特定的数据操作。

新手很有可能编写的代码结构

如果是我自己写的话,基本就是以下结构,并且我相信大部分人一上来也是先写了这段代码,后面再开始优化。

class Toggle extends React.Component {
  static defaultProps = {onToggle: () => {}}
  state = {on: false}
  toggle = () =>
    this.setState(
      ({on}) => ({on: !on}),
      () => this.props.onToggle(this.state.on),
    )
  render() {
    const { on } = this.state
    return (<div>
    <ToggleOn on={on}>The button is on</ToggleOn>
    <ToggleOff on={on}>The button is off</ToggleOff>
    <ToggleButton toggle={this.props.onToggle} />
    </div>
    )
  }
}

function App() {
  return (
  <Toggle onToggle={on => console.log('toggle', on)} />
  )
}

复制代码

我们再对比下就会发现,通过 cloneElemnt 确实可以让我们的结构更加清晰。 而且,还可以少一个包裹的 div 标签。虽然在 React16.2中出现了 <><> 的Fragment 表示可以避免此类问题,但是假设如果还有类似的10个组件需要接受相同的 props,我们要写10遍 on={on},我们应该减少重复劳动。

分析

如果有批量的子组件需要传递相同的props,那么使用 cloneElemnt 再好不过了。但需要注意的是,这也会给子组件传递一些不必要的数据。比如在这里的 demo 中,ToggleOn 和 ToggleOff 只需要接受 on/off 的props 即可,传入的 toggle 方法是冗余的。我们假设它又接受了与自身所需数据无关的 其他 props, 而该 props 又频繁变更,那么每一次都会得到这些 props 的组件都会 re-render 的可能。

参考资料

  1. https://reactjs.org/docs/react-api.html#cloneelement
  2. https://doc.react-china.org/docs/react-api.html
  3. https://mxstbr.blog/2017/02/react-children-deepdive/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值