参考链接:

https://juejin.cn/post/6844903815762673671

https://juejin.cn/post/6844904050236850184

前言

高阶组件与自定义hooks是React 目前流行的状态逻辑复用的两种解决方案

1.高阶组件是什么

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

高阶组件(HOC)是React中的高级技术,用来重用组件逻辑。但高阶组件本身并不是React API。它只是一种模式,这种模式是由React自身的组合性质必然产生的。

HOC简单例子:

//HOC
function visible(WrappedComponent) {return class extends Component {render() {const { visible, ...props } = this.props;if (visible === false) return null;return <WrappedComponent {...props} />;}}
}// 用HOC包裹组件
class Example extends Component {render() {return <span>示例组件</span>;}
}
export default HOC(Example)
//或者用decorator方式
@HOC
class Example extends Component {render() {return <span>示例组件</span>;}
}
export default Example//使用
<Example visible={false}/>

上面的代码就是一个HOC的简单应用,函数接收一个组件作为参数,并返回一个新组件,新组建可以接收一个visible props,根据visible的值来判断是否渲染传入的组件。

2 高阶组件实现方式

2.1属性代理

将一个React组件作为参数传入函数中,函数返回一个自定义的组件。该自定义组件的render函数中返回传入的React组件。

由此可以代理并操作传入的React组件的props,并且决定如何渲染,实际上 ,这种方式生成的高阶组件就是原组件的父组件,上面的函数visible就是一个HOC属性代理的实现方式。

这种实现方式下,HOC容器组件和传入组件的生命周期调用顺序和父,子组件的生命周期顺序是一致的。类似堆栈调用(先入后出

function proxyHOC(WrappedComponent) {return class extends Component {render() {return <WrappedComponent {...this.props} />;}}
}
//使用示例
class Example extends Component {render() {return <input name="name" {...this.props.name} />;}
}
export default HOC(Example)

通过属性代理实现的HOC可具有以下功能:

(1)操作props

可以对传入组件的props进行增加、修改、删除或者根据特定的props进行特殊的操作。

注意,使用HOC包裹后的组件,在给组件传入props时实际传入到了HOC的container容器组件中,如不需要操作props,请务必在容器组件中将props再度传给传入组件,否则传入组件不会接收到props

function proxyHOC(WrappedComponent) {return class Container extends Component {render() {const newProps = {...this.props,user: "ConardLi"}return <WrappedComponent {...newProps} />;}}
}

(2)获取refs引用

高阶组件中可获取传入组件的ref,通过ref获取组件的实例(即拿到传入组件实例的this),如下面的代码,当程序初始化完成后调用原组件的log方法。

function refHOC(WrappedComponent) {return class Container extends Component {componentDidMount() {this.wapperRef.log()}render() {return <WrappedComponent {...this.props} ref={ref => { this.wapperRef = ref }} />;}}
}

注意:HOC包裹的组件默认无法在外部调用时拿到原组件refs引用

虽然高阶组件的约定是将所有 props 传递给被包装组件,但这对于 refs 并不适用。那是因为 ref 实际上并不是一个 prop。就像 key 一样,它是由 React 专门处理的。如果将 ref 添加到 HOC 的返回组件中,则 ref 引用指向容器组件,而不是被包装组件。

这个问题的解决方案是通过使用 React.forwardRef API(React 16.3 中引入)。

(3)抽象state

// 高阶组件
function HOC(WrappedComponent) {return class Container extends React.Component {constructor(props) {super(props);this.state = {name: "",};this.onChange = this.onChange.bind(this);}onChange = (event) => {this.setState({name: event.target.value,})}render() {const newProps = {name: {value: this.state.name,onChange: this.onChange,},};return <WrappedComponent {...this.props} {...newProps} />;}};
}// 使用
class Example extends Component {render() {return <input name="name" {...this.props.name} />;}
}
export default HOC(Example)
//或者
@HOC
class Example extends Component {render() {return <input name="name" {...this.props.name} />;}
}

在这个例子中,我们把 input 组件中对 name这个prop在高阶组件中进行了重定义的覆盖(用value和onChange 代替),这就有效地抽象了同样的 state 操作。使得input组件由非受控组件变成了受控组件

(4)操作组件的static方法

可以对传入组件的static静态方法进行获取调用,增加、修改、删除

function refHOC(WrappedComponent) {return class Container extends Component {componentDidMount() {//获取static方法console.log(WrappedComponent.staticMethod)}//新增static方法WrappedComponent.addMethod1=()=>{}render() {return <WrappedComponent {...this.props} ref={ref => { this.wapperRef = ref }} />;}}
}

但当你将 HOC 应用于组件时,原始组件将使用容器组件进行包装。这意味着容器组件默认没有传入组件的任何静态方法,即无法在其他地方引入组件时拿到其静态方法。所以与props同理,请务必将传入组件的静态方法拷贝到容器组件上

(5)根据props实现条件渲染

根据特定的props决定传入组件是否渲染(如最上面的基本HOC例子)

function visibleHOC(WrappedComponent) {return class extends Component {render() {if (this.props.visible === false) return null;return <WrappedComponent {...props} />;}}
}

(6)用其他元素包裹传入的组件

在HOC的容器组件中将原组件通过其他元素再包裹起来,从而实现布局或者修改样式的目的:

function withBackgroundColor(WrappedComponent) {return class extends React.Component {render() {return (<div style={{ backgroundColor: "#ccc" }}><WrappedComponent {...this.props} {...newProps} /></div>);}};
}

2.2 反向继承

返回一个组件,该组件继承传入组件,在render中调用原组件的render。

由于继承了原组件,能通过this访问到原组件的生命周期、props、state、render等,相比属性代理它能操作更多的属性。

这种实现方式下,HOC组件和传入组件的生命周期调用顺序与队列类似(先进先出

function inheritHOC(WrappedComponent) {return class extends WrappedComponent {render() {return super.render();}}
}

通过反向继承实现的HOC,相比属性代理具有以下额外的功能:

(1)渲染劫持

渲染劫持指的就是高阶组件可以控制 WrappedComponent 的渲染过程,并渲染各种各样的结果。我们可以在这个过程中在任何 React 元素输出的结果中读取、增加、修改、删除 props,或读取或修改 React 元素树,或条件显示元素树,又或是用样式控制包裹元素树。

上面属性代理提到的条件渲染,其实也是渲染劫持的一种实现。

如果元素树中包括了函数类型的 React 组件,就不能操作组件的子组件

渲染劫持实示例:

function hijackHOC(WrappedComponent) {return class extends WrappedComponent {render() {const tree = super.render();let newProps = {};if (tree && tree.type === "input") {newProps = { value: "渲染被劫持了" };}const props = Object.assign({}, tree.props, newProps);const newTree = React.cloneElement(tree, props, tree.props.children);return newTree;}}
}

(2)劫持传入组件生命周期

因为反向继承方式实现的高阶组件返回的新组件是继承于传入组件,所以当新组件定义了同样的方法时,将会会覆盖父类(传入组件)的实例方法,如下面代码所示:

function HOC(WrappedComponent){// 继承了传入组件return class HOC extends WrappedComponent {// 注意:这里将重写 componentDidMount 方法componentDidMount(){...}render(){//使用 super 调用传入组件的 render 方法return super.render();}}
}

(3)操作传入组件state

反向继承方式实现的高阶组件中可以读取、编辑和删除传入组件实例中的 state,如下面代码所示:

function debugHOC(WrappedComponent) {return class extends WrappedComponent {render() {console.log("props", this.props);console.log("state", this.state);return (<div className="debuging">{super.render()}</div>)}}
}

操作传入组件的state可能会让 WrappedComponent 组件内部状态变得一团糟。大部分的高阶组件都应该限制读取或增加 state,尤其是后者,可以通过重新命名 state,以防止混淆。

2.3 两种方式对比

  • 属性代理是从“组合”的角度出发,这样有利于从外部去操作 WrappedComponent,可以操作的对象是 props,或者在 WrappedComponent 外面加一些拦截器,控制器等。
  • 反向继承则是从“继承”的角度出发,是从内部去操作 WrappedComponent,也就是可以操作组件内部的 state ,生命周期,render函数等等。

3. 高阶组件实际应用

(1)逻辑复用

多个页面组件存在代码结构和需求相似的情况,只是一些传参和数据不同,存在较多重复性代码。使用高阶组件进行统一包裹封装即可

下面是两个结构和需求相似的页面组件:

// views/PageA.js
import React from "react";
import fetchMovieListByType from "../lib/utils";
import MovieList from "../components/MovieList";class PageA extends React.Component {state = {movieList: [],}/* ... */async componentDidMount() {const movieList = await fetchMovieListByType("comedy");this.setState({movieList,});}render() {return <MovieList data={this.state.movieList} emptyTips="暂无喜剧"/>}
}
export default PageA;
// views/PageB.js
import React from "react";
import fetchMovieListByType from "../lib/utils";
import MovieList from "../components/MovieList";class PageB extends React.Component {state = {movieList: [],}// ...async componentDidMount() {const movieList = await fetchMovieListByType("action");this.setState({movieList,});}render() {return <MovieList data={this.state.movieList} emptyTips="暂无动作片"/>}
}
export default PageB;

将重复逻辑抽离成一个HOC:

// HOC
import React from "react";
const withFetchingHOC = (WrappedComponent, fetchingMethod, defaultProps) => {return class extends React.Component {async componentDidMount() {const data = await fetchingMethod();this.setState({data,});}render() {return (<WrappedComponent data={this.state.data} {...defaultProps} {...this.props} />);}}
}

使用示例:

// 使用:
// views/PageA.js
import React from "react";
import withFetchingHOC from "../hoc/withFetchingHOC";
import fetchMovieListByType from "../lib/utils";
import MovieList from "../components/MovieList";
const defaultProps = {emptyTips: "暂无喜剧"}export default withFetchingHOC(MovieList, fetchMovieListByType("comedy"), defaultProps);// views/PageB.js
import React from "react";
import withFetchingHOC from "../hoc/withFetchingHOC";
import fetchMovieListByType from "../lib/utils";
import MovieList from "../components/MovieList";
const defaultProps = {emptyTips: "暂无动作片"}export default withFetchingHOC(MovieList, fetchMovieListByType("action"), defaultProps);;// views/PageOthers.js
import React from "react";
import withFetchingHOC from "../hoc/withFetchingHOC";
import fetchMovieListByType from "../lib/utils";
import MovieList from "../components/MovieList";
const defaultProps = {...}export default withFetchingHOC(MovieList, fetchMovieListByType("some-other-type"), defaultProps);

上面设计的高阶组件 withFetchingHOC,把不一样的部分(组件和获取数据的方法) 抽离到外部作为传入,从而实现页面的复用。

(2)权限控制

function auth(WrappedComponent) {return class extends Component {render() {const { visible, auth, display = null, ...props } = this.props;if (visible === false || (auth && authList.indexOf(auth) === -1)) {return display}return <WrappedComponent {...props} />;}}
}

authList是我们在进入程序时向后端请求的所有权限列表,当组件所需要的权限不在传入权限列表中,或者设置的 visible是false,我们将其显示为传入的组件样式,或者null。我们可以将任何需要进行权限校验的组件应用HOC:

  @authclass Input extends Component {  ...  }@authclass Button extends Component {  ...  }<Button auth="user/addUser">添加用户</Button><Input auth="user/search" visible={false} >添加用户</Input>

4. 其他技巧:

(1)高阶组件参数

有时,我们调用高阶组件时需要传入一些参数,这可以用非常简单的方式来实现:


import React, { Component } from "React";
function HOCFactoryFactory(...params) { // 可以做一些改变 params 的事return function HOCFactory(WrappedComponent) { return class HOC extends Component { render() { return <WrappedComponent {...this.props} />; } } }
}

当你使用的时候,可以这么写:

HOCFactoryFactory(params)(WrappedComponent)
// 或者
@HOCFatoryFactory(params)
class WrappedComponent extends React.Component{}

(2)高阶组件命名

当包裹一个高阶组件时,我们失去了原始 WrappedComponent 的 displayName,而组件名字是方便我们开发与调试的重要属性

HOC.displayName = `HOC(${getDisplayName(WrappedComponent)})`;
// 或者
class HOC extends ... { static displayName = `HOC(${getDisplayName(WrappedComponent)})`; ...

然后通过HOC.displayName来获取即可

React 高阶组件HOC详解相关推荐

  1. [react] 高阶组件(HOC)有哪些优点和缺点?

    [react] 高阶组件(HOC)有哪些优点和缺点? HOC 优点 通过传递props去影响内层组件的状态,不直接改变内层组件的状态,降低了耦合度 缺点 组件多层嵌套, 增加复杂度与理解成本 ref隔 ...

  2. 「react进阶」一文吃透React高阶组件(HOC)

    一 前言 React高阶组件(HOC),对于很多react开发者来说并不陌生,它是灵活使用react组件的一种技巧,高阶组件本身不是组件,它是一个参数为组件,返回值也是一个组件的函数.高阶作用用于强化 ...

  3. react 高阶组件hoc使用

    react 高阶组件hoc使用 1. 为什么使用高阶组件 2. 具体使用 2.1原代码: 2.2 使用hoc封装后 1. 为什么使用高阶组件 高阶组件(HOC)是 React 中用于复用组件逻辑的一种 ...

  4. react 高阶组件HOC实现组件复用

    目录 一 使用步骤 二 显示鼠标坐标 三 鼠标随图片移动 四 设置displayName 五 传递props 高阶组件HOC: Higher-Order Component,是一个函数,接收要包装的组 ...

  5. React 高阶组件HOC使用总结

    HOC(高阶组件) /*HOC(高阶组件): 接收一个组件,返回包装后的组件(增强组件)- 不是React API- 是一种设计模式,类似于装饰器模式- ≈ Mixin && > ...

  6. React高阶组件(HOC)的写法归纳

    react高阶组件的写法总结 什么是高阶组件 高阶组件要解决什么问题 高阶组件的写法 什么是高阶组件 何为高阶组件(HOC),根据官方文档的解释:"高阶组件是react中复用组件逻辑的一项高 ...

  7. 条件渲染之react高阶组件——HOC

    定义:它接受任何输入(多数时候是一个组件)并返回一个组件作为输出.返回的组件是输入组件的增强版本,并且可以在 JSX 中使用.不是react api而是一种设计模式. 作用:强化组件,复用逻辑,提升渲 ...

  8. React 高阶组件HOC、设置displayName、高阶组件传递props

    文章目录 1. 高阶组件 使用步骤 2. 高阶组件设置displayName 3.传递props 1. 高阶组件 目的:实现状态逻辑复用.实现功能增强.返回一个增强组件. 高阶组件(HOC,Highe ...

  9. React高阶组件(HOC)模型理论与实践

    什么是HOC? HOC(全称Higher-order component)是一种React的进阶使用方法,主要还是为了便于组件的复用.HOC就是一个方法,获取一个组件,返回一个更高级的组件. 什么时候 ...

最新文章

  1. TensorFlow-Bitcoin-Robot:Tensorflow 比特币交易机器人
  2. java 实体类 代码重复_java – JPA两个单向@OneToMany关系到同一个实体导致重复输入...
  3. MMP,我说每年年会我怎么老是中不了奖,原来是这样
  4. 文件服务器报告,文件服务器报告
  5. 操作系统概念第九版编程项目:Linux内核模块
  6. 【python】数据挖掘 实验:中国二级城市经纬度聚类分析
  7. OFDM子载波频率 知乎_频谱中射频干扰信号流化、分析与回放
  8. cocos2d-x 音乐/音效设置
  9. NetVLAD: CNN architecture for weakly supervised place recognition 翻译
  10. 【ABMDRNet2021】 Adaptive-weighted Bi-directional Modality Difference Reduction Network for RGB-T Sema
  11. 命令行解析模块 以及 metavar 和dest的理解
  12. 未转变者3.x局域网服务器,未转变者局域网怎么创建服务器
  13. 微信小程序接入富文本编辑
  14. uniapp部分安卓字体加粗问题
  15. 3dmax怎么建模眼镜盒模型_ 3dmax盒子建模过程 小白教程
  16. jmeter测试并发
  17. WPF/Silverlight深度解决方案:(二)Silverlight源码之自我保护
  18. 文本语音朗读电子书 2005 免费
  19. Elasticsearch初步学习(仿京东搜索、爬虫)
  20. java小游戏龟兔赛跑,Java_多线程_简单模拟龟兔赛跑

热门文章

  1. 做电商网站如何选购云服务器?
  2. 算法与数据结构07:前缀树,计数排序与桶排序
  3. 华云数据出席2021信创发展论坛:喜获信创双项殊荣 发布业内首个《信创云基座白皮书》
  4. 腾讯安全与青藤云安全联合发布“天眼云镜”主机安全产品
  5. element-ui中点击菜单,改变当前菜单背景颜色
  6. 乐理基础知识-1.节奏
  7. JavaScript反调试技巧
  8. 举个栗子~Tableau 技巧(215):简化实现雷达图(Radar Chart)
  9. 闭关修炼30天,“啃透”这658页PDF,成功定级阿里P7
  10. Ios王者微信抢先服务器,王者荣耀:iOS微信用户抢先体验!国服出装铭文,点击一键查看!...