React之高阶组件

React之高阶组件React 之高阶组件 React 之高阶组件前言基本用法属性代理模式 PropsProxy 通过 ref 访问组件实例反向继承 InheritanceI 总结 React 之高阶组件前言高阶组件就是一个函数 且该函数接受一个组件作为参数 并返回一个新的组件从高阶组件的定义来看 高阶组件并不是一个组件 它就是一个函数 接受一个组件并且返回一个被包装过的新组件 const

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的父组件中使用
的时候,可以通过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之高阶组件

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/218449.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 上午7:14
下一篇 2026年3月18日 上午7:14


相关推荐

  • js 取模 取余

    var i=10;var j=3;var mo=Math.floor(i/j);var yu=i%j;

    2022年4月9日
    44
  • Ubuntu下安装eclipse

    Ubuntu下安装eclipse

    2021年12月2日
    44
  • 指令周期、中断周期、机器周期(CPU周期)、时钟周期

    指令周期、中断周期、机器周期(CPU周期)、时钟周期指令周期 CPU 每取出并执行一条指令所需的全部时间叫指令周期 也即 CPU 完成一条指令的时间叫指令周期一般一条完整的指令包括 取指周期 间址周期 执行周期 中断周期 JMPX 该指令的指令周期只有取指周期 ADDX 该指令只有取指周期 执行周期 一个指令周期包含的机器周期个数亦与指令所要求的动作有关 如单操作数指令 只需要一个取操作数周期 而双操作数指令需要两个取操作数周期 实

    2026年3月18日
    2
  • 国密SM4分组加密[通俗易懂]

    国密SM4分组加密[通俗易懂]分享一篇SM4加密算法实现文章,算法用C语言即可实现,只有短短300多行代码。SMS4是我国无线局域网标准WAPI中所采用的分组密码标准,随后被我国商用密码标准采用,又名SM4(SM是“商密”的缩写,目前公布的其他商密标准包括SM2椭圆曲线公钥密码,SM3密码杂凑算法)。作为我国商用密码的分组密码标准,预计SMS4在国内的敏感但非机密的应用领域会逐渐取代3DES,AES等国外分组密码标准,用于通…

    2026年4月15日
    6
  • Java Jsoup 解析处理百度谷歌搜索结果的示例代码

    Java Jsoup 解析处理百度谷歌搜索结果的示例代码本文主要介绍Java中,通过Jsoup来解析百度和谷歌中的搜索结果,获取搜索到的链接url和标题title的方法,以及相关的示例代码。原文地址:JavaJsoup解析处理百度谷歌搜索结果的示例代码

    2022年6月15日
    48
  • 对《Java核心技术卷一》读者的一些建议

    对《Java核心技术卷一》读者的一些建议思想不会变 所以 Java 编程思想 还停留在第 4 版 2007 年 而技术是要更新迭代的 所以 Java 核心技术卷一 来到了第十版 2016 年 我们来看一下 Java 核心技术卷一 第十版的大纲 前九章 包括 Java 程序设计概述 Java 程序设计环境 Java 的基本程序设计结构 对象与类 继承 接口 lambda 表达式与内部类 异常 断言和日

    2026年3月19日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号