专栏名称: 大转转FE
定期分享一些团队对前端的想法与沉淀
目录
相关文章推荐
温岭市场监管  ·  “行走的食安课堂”走进田间地头 ·  7 小时前  
温岭市场监管  ·  “行走的食安课堂”走进田间地头 ·  7 小时前  
中国药闻  ·  李强主持召开国务院常务会议 ... ·  昨天  
FoodMate Global  ·  学校食堂禁用、慎用食品清单 ·  2 天前  
51好读  ›  专栏  ›  大转转FE

聊聊React高阶组件(Higher-Order Components)

大转转FE  · 公众号  ·  · 2017-09-19 17:50

正文

使用 react 已经有不短的时间了,最近看到关于 react 高阶组件的一篇文章,看了之后顿时眼前一亮,对于我这种还在新手村晃荡、一切朝着打怪升级看齐的小喽啰来说,像这种难度不是太高同时门槛也不是那么低的东西如今可不多见了啊,是个不可多得的 zhuangbility 的利器,自然不可轻易错过,遂深入了解了一番。


概述

高阶组件的定义

React 官网上对高阶组件的定义:

高阶部件是一种用于复用组件逻辑的高级技术,它并不是 React API的一部分,而是从 React 演化而来的一种模式。 具体地说,高阶组件就是一个接收一个组件并返回另外一个新组件的函数。 相比于普通组件将 props 转化成界面UI,高阶组件将一个普通组件转化为另外一个组件。

大概意思就是说, HOC 并不是 react `` API 的一部分,而是一种实现的模式,有点类似于 观察者模式 单例模式 之类的东西,本质还是函数。


功能

既然是能够拿来 zhuangbility 的利器,那么不管怎么说,简单实用的招式必不可少,可以利用高阶组件来做的事情:

  1. 代码复用,逻辑抽象,抽离底层准备( bootstrap )代码

  2. Props 更改

  3. State 抽象和更改

  4. 渲染劫持


用法示例

基本用法

  • 一个最简单的高阶组件( HOC ) 示例如下:

  1. // HOCComponent.js

  2. import React from 'react'

  3. export default PackagedComponent =>

  4.  class HOC extends React.Component {

  5.    render() {

  6.      return (

  7.        <div id="HOCWrapper">

  8.          

  9.            

    Titleh1>

  10.          header>

  11.          <PackagedComponent/>

  12.        div>

  13.       )

  14.    }

  15.  }

此文件导出了一个函数,此函数返回经过一个经过处理的组件,它接受一个参数 PackagedComponent ,此参数就是将要被 HOC 包装的普通组件,接受一个普通组件,返回另外一个新的组件,很符合高阶组件的定义。

  • 此高阶组件的简单使用如下:

  1. // main.js

  2. import React from 'react'

  3. // (1)

  4. import HOCComponent from './HOCComponent'

  5. // (2)

  6. @HOCComponent

  7. class Main extends React.Component {

  8.  render() {

  9.    return(

  10.      

  11.        

    main contentp>

  12.      main>

  13.    )

  14.  }

  15. }

  16. // (2)

  17. // 也可以将上面的 @HOCComponent换成下面这句

  18. //  const MainComponent = HOCComponent(Main)

  19. export default MainComponent

想要使用高阶组件,首先 ( 1 ) 将高阶组件导入,然后 ( 2 ) 使用此组件包装需要被包装的普通组件 Main ,这里的 @ 符号是 ES7 中的 decorator ,写过 Java 或者其他静态语言的同学应该并不陌生,这实际上就是一个语法糖,可以使用 react-decorators 进行转换, 在这里相当于下面这句代码:

  1. const MainComponent = HOCComponent(Main)

@HOCComponent 完全可以换成上面那句,只不过需要注意的是,类不具有提升的能力,所以若是觉得上面那句顺眼换一下,那么在换过之后,还要将这一句的位置移到类 Main 定义的后面。

最后,导出的是被高阶组件处理过的组件 MainComponent

  • 这样,就完成了一个普通组件的包装,可以在页面上将被包装过的组件显示出来了:

  1. import React from 'react'

  2. import { render } from 'react-dom'

  3. // 导入组件

  4. import MainComponent from './main'

  5. render(

  6.  <MainComponent />,

  7.  document.getElementById('root')

  8. )

页面显示如下:

可以使用 React Developer Tools 查看页面结构:

可以看出,组件 Main 的外面包装了一层 HOC ,有点类似于父组件和子组件,但很显然高阶组件并不等于父组件。

另外需要注意的一点, HOC 这个高阶组件,我们可能会用到不止一次,功能技术上没什么关系,但是不利于调试,为了快速地区分出某个普通组件的所属的 HOC 到底是哪一个,我们可以给这些 HOC 进行命名:

  1. // 获取传入的被包装的组件名称,以便为 HOC 进行命名

  2. let getDisplayName = component => {

  3.  return component.displayName || component.name || 'Component'

  4. }

  5. export default PackagedComponent =>

  6.   class HOC extends React.Component {

  7.    // 这里的 displayName就指的是 HOC的显示名称,我们将它重新定义了一遍

  8.    // static被 stage-0  stage-1 和 stage-2所支持

  9.    static displayName = `HOC(${getDisplayName(PackagedComponent)})`

  10.    render() {

  11.      return (

  12.        <div id= "HOCWrapper">

  13.          

  14.            

    Titleh1>

  15.          header>

  16.          <PackagedComponent/>

  17.        div>

  18.      )

  19.     }

  20.  }

现在的 DOM结构:

可以看到,原先的 HOC 已经变成了 HOC ( Main ) 了,这么做主要是利于我们的调试开发。

这里的 HOC ,可以看做是一个简单的为普通组件增加 Title 的高阶组件,但是很明显并不是所有的页面都只使用同一个标题,标题必须要可定制化才符合实际情况。

想做到这一点也很简单,那就是再为 HOC 组件的高阶函数增加一个 title 参数,另外考虑到 柯里化 Curry 函数和函数式编程,我们修改后的 HOC 代码如下:

  1. // HOCComponent.js

  2. // 增加了一个函数,这个函数存在一个参数,此参数就是要传入的`title`

  3. export default PackagedComponent => componentTitle =>

  4.  class HOC extends React.Component {

  5.     static displayName = `HOC(${getDisplayName(PackagedComponent)})`

  6.    render() {

  7.      return (

  8.        <div id="HOCWrapper">

  9.          

  10.            

    { componentTitle ? componentTitle : 'Title' }h1>

  11.          header>

  12.          <PackagedComponent/>

  13.        div>

  14.      )

  15.    }

  16.   }

使用方式如下:

  1. // main.js

  2. // ...省略代码

  3. const MainComponent = HOCComponent(Main)('首页')

  4. export default MainComponent

然后在页面上就可以看到效果了:


属性代理

HOC 是包裹在普通组件外面的一层高阶函数,任何要传入普通组件内的 props 或者 state 首先都要经过 HOC

props state 等属性原本是要流向 目标组件的腰包的,但是却被 雁过拔毛的 HOC 拦路打劫,那么最终这些 props states 数据到底还能不能再到达 目标组件,或者哪些能到达以及到达多少就全由 HOC 说了算了,也就是说, HOC 拥有了提前对这些属性进行修改的能力。

更改 Props

Props 的更改操作包括 增、删、改、查 ,在修改和删除 Props 的时候需要注意,除非特殊要求,否则最好不要影响到原本传递给普通组件的 Props

  1. class HOC extends React.Component {

  2.    static displayName = `HOC(${getDisplayName(PackagedComponent)})`

  3.    render () {

  4.      // 向普通组件增加了一个新的 `Props`

  5.      const newProps = {

  6.        summary: '这是内容'

  7.      }

  8.      return (

  9.         <div id="HOCWrapper">

  10.          

  11.            

    { componentTitle ? componentTitle : 'Title' }h1>

  12.          header>

  13.          <PackagedComponent {... this.props} {...newProps}/>

  14.        div>

  15.      )

  16.    }

  17.  }


通过 refs 获取组件实例

普通组件如果带有一个 ref 属性,当其通过 HOC 的处理后,已经无法通过类似 this . refs . component 的形式获取到这个普通组件了,只会得到一个被处理之后的组件,想要仍然获得原先的普通组件,需要对 ref 进行处理,一种处理方法类似于 react - readux 中的 connect 方法,如下:

  1. // HOCComponnet.js

  2. ...

  3. export default PackagedComponent => componentTitle =>

  4.   class HOC extends React.Component {

  5.    static displayName = `HOC(${getDisplayName(PackagedComponent)})`

  6.    // 回调方法,当被包装组件渲染完毕后,调用被包装组件的 changeColor 方法

  7.    propc(wrapperComponentInstance) {

  8.      wrapperComponentInstance.changeColor()

  9.    }

  10.    render() {

  11.      // 改变 props,使用 ref 获取被包装组件的示例,以调用其中的方法

  12.      const props = Object.assign({}, this.props, {ref: this.propc.bind(this)})

  13.      return (

  14.         <div id="HOCWrapper">

  15.          

  16.            

    { componentTitle ? componentTitle : 'Title' }h1>

  17.          header>

  18.          <PackagedComponent {...props}/>

  19.        div>

  20.      )

  21.    }

  22.  }

使用:

  1. // main.js

  2. ...

  3. class Main extends React.Component {

  4.  render() {

  5.    return(

  6.      

  7.        

    main contentp>

  8.        { this.props.summary }span>

  9.      main>

  10.    )

  11.  }

  12.  // main.js 中的changeColor 方法

  13.  changeColor() {

  14.    console.log(666);

  15.    document.querySelector('p').style.color = 'greenyellow'







请到「今天看啥」查看全文