react高阶组件和render props

182 阅读3分钟

高阶组件HOC(High order components)

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

接收函数作为输入,或者输出为函数的一类函数成为高阶函数,相应的高阶组件就是参数为组件,返回也是组件的一类函数,高阶组件本质上是一个函数。

简单的示例:

const wrapper = (Component) => {
  const wrappedComponent = (props) => {
    const {age} = props
    const check = () => {
      if(age < 18){
        return 'kid'
      }else{
        return 'adult'
      }
    }
    return <div className='wrapper'>
      {check()}
      <Component {...props}/>
    </div>
  }
  return wrappedComponent
}

wrapper方法的入参是一个组件,返回定制化的组件wrappedComponent,props是Component组件的入参,定义一个check方法,根据入参做一些逻辑判断,并更新到wrappedComponent组件里。

使用的时候,先定义要作为入参的组件,例如叫ChildA,然后用wrapper方法重新组装一下,生成一个新组件NewComponent,直接使用这个新组件即可,传参也放在newComponent传入。可以创建多个新组件,这些组件共用了check的逻辑。

完整代码:

import './App.css';
import React from 'react';

const wrapper = (Component) => {
  const wrappedComponent = (props) => {
    const {age} = props
    const checkFun = () => {
      if(age < 18){
        return 'kid'
      }else{
        return 'adult'
      }
    }
    return <div className='wrapper'>
      {checkFun()}
      <Component {...props}/>
    </div>
  }
  return wrappedComponent
}
function App() {
  const ChildA = (props) => {
    return <div>
      {props.age}
    </div>
  }
  const NewComponent = wrapper(ChildA)
  return(
    <div>
      <NewComponent age={18}/>
    </div>
  )
}

export default App;

render props

高阶组件的使用姿势是用“函数”包裹“组件”,而 render props 恰恰相反,它强调的是用“组件”包裹“函数” ,另外并不是该模式叫做render Props就必须使用名为render的props,实际上可以命名任意的props。

上面的例子改写一下:

const RenderComponent = (props) => {
  const {age} = props
  const check = () => {
    if(age < 18){
      return 'kid'
    }else{
      return 'adult'
    }
  }
  return props.checkResult(check())
}

RenderComponent的props有2个参数,一个是age,另外一个是checkResult,这里特意没有用render关键字,而是用checkResult。并且RenderComponent返回的是checkResult,在使用的时候直接用组件RenderComponent包裹checkResult的回调函数。

完整代码

const RenderComponent = (props) => {
  const {age} = props
  const check = () => {
    if(typeof age === 'undefined'){
      return 'undefined'
    }
    if(age < 18){
      return 'kid'
    }else{
      return 'adult'
    }
  }
  return props.checkResult(check())
}
function App() {
  return(
    <div>
      <RenderComponent age={18} checkResult={(data) => {
        return (
          <div>
            {data}
            <div>18</div>
          </div>
        )
      }} />
    </div>
  )
}

render props更加语义化一些是用children替代props参数,即用children替代checkResult ,RenderComponent渲染子组件则用children的方式

const RenderComponent = (props) => {
  const {age} = props
  const check = () => {
    if(typeof age === 'undefined'){
      return 'undefined'
    }
    if(age < 18){
      return 'kid'
    }else{
      return 'adult'
    }
  }
  return props.children(check())
}
function App() {
  return(
    <div>
      <RenderComponent age={18}>
        {
          (data) => {
            return (
              <div>
                {data}
                <div>18</div>
              </div>
            )
          }
        }
      </RenderComponent>
    </div>
  )
}

export default App;

这样看起来更容易理解一些。

render props比HOC更灵活

在高阶组件中,目标组件对于数据的获取没有主动权,数据的分发逻辑全部收敛在高阶组件的内部;而在 render props 中,除了父组件可以对数据进行分发处理之外,子组件也可以选择性地对数据进行接收。

在软件设计模式中,有一个非常重要的原则,叫“开放封闭原则”。一个好的模式,应该尽可能做到对拓展开放,对修改封闭

不足

无论是高阶组件,还是 render props,两者的出现都是为了弥补类组件在“逻辑复用”这个层面的不灵活性。它们各自都有着自己的不足,这些不足包括但不限于以下几点:

  1. 嵌套地狱问题,当嵌套层级过多后,数据源的追溯会变得十分困难
  2. 较高的学习成本
  3. props 属性命名冲突问题

当设计模式解决不了问题时,我们本能地需要从编程模式上寻找答案,于是Hooks大行其道。Hooks 能够很好地规避掉旧时类组件中各种设计模式带来的弊端,比如说它不存在嵌套地狱,允许属性重命名、允许我们在任何需要它的地方引入并访问目标状态等。