React之高阶组件

  • React之高阶组件
    • 前言
    • 基本用法
      • 属性代理模式(Props Proxy)
        • 通过ref访问组件实例
      • 反向继承(Inheritance Inversion)
      • 总结

React之高阶组件

前言

高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件

从高阶组件的定义来看,高阶组件并不是一个组件,它就是一个函数,接受一个组件并且返回一个被包装过的新组件。

const NewComponent = higherOrderComponent(OldComponent)

React的高阶组件在React的第三方库中的应用是非常广泛的,理解好React的高阶组件,有利于我们今后更好的学习React第三方库的源码,可以让我们写出复用性更强的代码。

基本用法

React高阶组件的常见形式一般有两种,分别是属性代理(Props Proxy)的形式和反向继承(Inheritance Inversion),简称II。下面分别介绍两种形式的用法。

属性代理模式(Props Proxy)

代理模式中你可以读取、添加、修改、删除将要传递给 WrappedComponentprops

假设我们有一个组件,用来从localStorage中获取数据,我们可以这么做:

import React, { Component } from 'react';class GetName extends Component {constructor(props) {super(props);this.state = {name: '',};}componentWillMount() {const name = localStorage.getItem('name');this.setState({name});}render() {return (<p>{ this.state.name }</p>)}
}

这是常规的写法,但是如果此时也有其他组件需要从localStorage中获取数据,那么组件中从localStorage中取值的部分代码就会重复。那么有什么比较好的方法可以复用这部分代码呢?当然有,下面用React属性代理模式来重写上面的代码。

import React, { Component } from 'react';function componentWrapper(WrappedComponent) {class HOC extends Component {constructor(props) {super(props);this.state = {name: ''}}componentWillMount() {const name = localStorage.getItem('name');this.setState({name});}render () {const newProps = {name:  this.state.name}return (<WrappedComponent {...this.props} {...newProps}  />)}}return NewComponent;
}class ShowName extends Component {render() {return (<h1> { this.props.name }</h1>)}
}
const ShowNameWidthData = componentWrapper(ShowName);

这里我先定义一个函数componentWrapper,函数的参数里面传入需要包装的组件WrappedComponent,再在函数里面定义一个新的组件HOC并返回出来,在这个新的组件里面可以添加获取localStorage数据的逻辑,最后将获取到的数据通过属性传递的方式传给被包装的组件WrappedComponent,这样被返回的组件中就可以通过this.props.name的方式获取到localStorage中存储的数据了。

这里存在一个问题,代码中从localStorage中获取的key值是固定的,是name,如果我想获取其他key的值呢?我们可以通过给高阶组件传入参数的方式来指定获取的key值,代码可以这么写:

import React, { Component } from 'react';function componentWrapper(WrappedComponent, key) {class HOC extends Component {constructor(props) {super(props);this.state = {data: ''}}componentWillMount() {const data = localStorage.getItem(key);this.setState({data});}render () {const newProps = {data: this.state.data}return (<WrappedComponent{...this.props} {...newProps}  />)}}return NewComponent;
}class ShowName extends Component {render() {return (<h1> { this.props.data }</h1>)}
}const ShowNameWidthData = componentWrapper(ShowName, 'age');

可以看到,我们给高阶组件中传入了第二个参数来指定key值,很好的解决了上述问题,这么写没问题,但是我们我们经常在React的第三方库中看到如下用法:

HOC([param])([WrappedComponent])

这其实是函数柯里化的写法func(a, b) => func(a)(b),下面我用函数柯里化的形式来改写上面的代码:

import React, { Component } from 'react';function componentWrapper (key) {return function(WrappedComponent) {return class HOC extends Component {constructor(props) {super(props);this.state = {data: ''}}componentWillMount() {const data = localStorage.getItem(key);this.setState({data});}render () {return (<WrappedComponentdata={this.state.data} />)}}}
}class ShowName extends Component {render() {return (<h1>{ this.props.data }</h1>)}
}
const ShowNameWidthData = componentWrapper('age')(ShowName);
export default ShowNameWidthData;

如果熟悉es6箭头函数(不熟悉的可以戳这里~~)的写法来改造componentWrapper会更加简洁:

function componentWrapper = (key) => (WrappedComponent)=> {return class HOC extends Component {// ...省略}
}

我们在使用react-redux的时候一般会这么使用:

connect(mapStateToProps, mapDispatchToProps)(TodoApp)

看着是不是很熟悉?没错,这就是高阶函数的典型应用之一。connect函数的作用连接React组件和redux store,通过mapStateToProps允许我们将 store 中的数据作为 props 绑定到TodoApp组件上,mapDispatchToPropsaction 作为 props 绑定到组件上,也会成为 TodoApp组件的 props

如果我们在浏览器中用React Developer Tools查看React的组件树可以看到showName组件被HOC包裹起来了。

但是随之带来的问题是,如果这个高阶组件被使用了多次,那么在调试的时候,将会看到一大堆HOC,针对这个问题我们可以进行优化,能够同时显示被高阶组件包裹的组件名称。这里可以通过新增一个getDisplayName函数以及静态属性displayName,修改后的代码如下:

import React, { Component } from 'react';const getDisplayName = component => component.displayName || component.name || 'Component';
function componentWrapper (key) {return function(WrappedComponent) {return class HOC extends Component {// 定义一个静态方法displayName来显示原有组件的名称static displayName = `HOC(${getDisplayName(WrappedComponent)})`;constructor(props) {super(props);this.state = {data: ''}}componentWillMount() {const data = localStorage.getItem(key);this.setState({data});}render () {return (<WrappedComponent data={this.state.data} />)}}}
}class ShowName extends Component {render() {return (<h1>{ this.props.data }</h1>)}
}
const ShowNameWidthData = componentWrapper('age')(ShowName);

此时再去观察组件树,被包裹组件的名称被显示上了。

通过ref访问组件实例

一般如果我们想在React的父组件中使用<component ref="myComponent"/>的时候,可以通过this.refs.myComponent的形式获取到组件真正实例的引用。但是如果一个组件经过高阶组件的包装就无法获得WrappedComponentref了,因为我们这时候拿到的是HOC里面返回的component

下面提供一个解决方法:

要想通过 ref获取WrappedComponent 的实例,必须在React高阶组件的render方法中返回WrappedComponent

在父组件中想要获取到WrappedComponent 的实例,先定义一个getInstance方法,并将这个方法作为属性传入高阶组件。高阶组件中当执行render方法的时候会先判断传入的getInstance 这个属性是否是函数,如果是就返回WrappedComponent对象的引用。

import React, { Component } from 'react';function HOCWrapper(WrappedComponent) {return class HOC extends Component {render() {let props = {...this.props};if (typeof this.props.getInstance === "function") {props.ref = this.props.getInstance;}return <WrappedComponent {...props} />}}
}class MyComponent extends Component {render() {return (<div name="bob">this is a demo</div>)}
}const Demo = HOCWrapper(MyComponent);class ParentComponent extends React.Component {getInstance = (ref)=>{this.wrappedInstance = ref;}componentDidMount() {console.log(this.wrappedInstance)}render(){return <Demo getInstance={this.getInstance} />}
}

反向继承(Inheritance Inversion)

反向继承顾名思义就是返回的高阶组件继承了WrappedComponent。反向继承允许高阶组件通过 this关键字获取 WrappedComponent,意味着它可以获取到 stateprops,组件生命周期(component lifecycle)钩子,以及渲染方法(render)

反向继承的应用之一是"渲染劫持",所谓"渲染劫持"就是高阶组件中由于继承了WrappedComponent,因此就控制了它的render方法,利用这点可以做各种操作。看一个经典的例子。

import React, { Component } from 'react';function iiHOC(config) {return function(WrappedComponent) {return class Enhancer extends WrappedComponent {render() {const elementTree = super.render();const {type, style = {}} = config;if(type === 'app-style') {return (<div style={{ ...style }}>{ elementTree }</div>)}return elementTree;}}}
}class MyComponent extends Component {render() {return (<div>我来啦 ~~ </div>)}
}export default iiHOC({type: 'app-style', style: {color: 'red', 'font-size': '20px'}})(MyComponent);

这个例子通过传入的config参数中的type值来判断是否加入样式渲染。注意这个这个高阶函数的写法,用到了函数的柯里化,主要是为了便于传参。

总结

关于React的高阶函数的总结就到这里,React的高阶函数其实本质上和其他语言中的装饰器的概念是一致的,只是一种设计模式的相互借鉴,掌握好React高阶组件的用法可以帮助我们很好的提高代码的封装性,由于很多React的第三方库中也大量使用了高阶组件的设计方法,对于我们以后阅读源码也更加游刃有余。

更多精彩内容欢迎关注我的公众号!

React之高阶组件相关推荐

  1. [react] 写一个react的高阶组件并说明你对高阶组件的理解

    [react] 写一个react的高阶组件并说明你对高阶组件的理解 定义高阶组件 import React, { Component } from 'react';const simpleHoc = ...

  2. [react] 使用高阶组件(HOC)实现一个loading组件

    [react] 使用高阶组件(HOC)实现一个loading组件 function HOC(wrappedComponent) {return class extends React.Componen ...

  3. React的高阶组件详解

    文章目录 React的高阶组件 高阶组件基本介绍 高阶组件使用过程 高阶组件应用场景 高阶组件的意义 React的高阶组件 高阶组件基本介绍 什么是高阶组件呢? 在认识高阶组价之前, 我们先来回顾一下 ...

  4. React 之 高阶组件的理解

    1.基本概念 高阶组件是参数为组件,返回值为新组件的函数. 2.举例说明 ① 装饰工厂模式 组件是 react 中的基本单元,组件中通常有一些逻辑(非渲染)需要复用处理.这里我们可以用高阶组件对组件内 ...

  5. react学习—高阶组件HOC

    高阶组件HOC 一.高阶组件HOC 1.高阶组件 2.属性传递 3.使用属性传递 4.注意 一.高阶组件HOC HOF:Higher-Order Function, 高阶函数,以函数作为参数,并返回一 ...

  6. React的高阶组件(HOC)的构建与应用

    高阶组件 ​ 高阶组件(HOC)是react中对组件逻辑进行重用的高级技术.但高阶组件本身并不是React API.它只是一种模式,这种模式是由react自身的组合性质必然产生的. ​ 具体而言,高阶 ...

  7. React HOC高阶组件

    在React组件的构建过程中,常常有这样的场景,有一类功能需要被不同的组件公用.(React 中用于重用组件逻辑的高级技巧) HOC--参数是组件同时返回值也是组件 HOC不仅仅是一个方法,确切说应该 ...

  8. React的高阶组件(HOC)

    1. 概念 高阶组件和高阶函数的类似,使用函数接收一个组件,并返回一个组件. function withList(WrapComponent) {return class extends Compon ...

  9. react初始高阶组件

    首先 我们要了解什么是高阶组件 第一 高阶组件必须是一个函数 第二 高阶组件接收一个参数,这个参数也必须是一个组件 第三 他的返回值 也是一个组件 至于高阶组件的作用 我们后续会讲解 本文只是带大家认 ...

最新文章

  1. SMS主站点配置详细图解:Sms2003系列之二
  2. 艾伟_转载:探索.Net中的委托
  3. Cell:损伤和微生物模式的共同作用控制着根部的局部免疫反应
  4. protoc gen php,protoc-gen-php --php_out: protoc-gen-php: Plugin output is unparseable.
  5. struts2 action间跳转传值
  6. win7下删除提示没权限删除文件的方法
  7. python3解释器执行not 1 and 1_编程语言的分类,python解释器多版本共存.执行python的两种方式,变量,用户与程序交互...
  8. java音乐登陆界面_第四篇——Spring音乐登录界面设计及实现(C#)
  9. vue使用js-cookie写入获取不到_Vue 面向对象 - 实战 - 内容管理系统(五)
  10. JSPServlet相关
  11. Java数据库编程---JDBC操作步骤及数据库连接操作
  12. java 调用 easypr_EasyPR-Java项目maven版本所需jar包
  13. 双击 计算机 打不开,电脑双击桌面图标打不开的解决方法
  14. 利用 IP 扩展访问列表实现应用服务的访问限制
  15. TOJ 1320.Billiard
  16. varnish 缓存php,php实现监控varnish缓存服务器的状态
  17. Java之------常用的设计模式
  18. 详解多级目标检测体系结构Cascade RCNN
  19. 电脑音频服务器未修复咋办,音频服务未运行怎么办?win7和win10电脑没声音了恢复方法...
  20. Linux桥mac地址表(Hash表)结构

热门文章

  1. 企业微信机器人推送mysql_进阶功能|将数据推送到企业微信群机器人
  2. Python爬取拉勾网招聘信息
  3. vue-router.mjs:798 Uncaught (in promise) Error: No match for
  4. 室内导航--机器视觉、ROS、Goseek(五)Goseek 虚拟室内环境准备
  5. JavaScript学习目录
  6. nginx安装相关依赖
  7. 带着机器狗遛弯是什么体验?
  8. vxlan报文 wireshark_配置 VXLAN
  9. php百度人脸检测api测颜值评分(源码直接可用)
  10. linux运维之道 第二章 2.1.1目录文件基本操作