# React高阶组件

# 【什么是高阶组件】

官方的定义:高阶组件是参数为组件,返回值为新组件的函数

这个概念类似于JavaScript中的高阶函数的概念,高阶函数的维基百科定义:至少满足以下条件之一:①接受一个或多个函数作为输入;②输出一个函数;JavaScript中比较常见的filter、map、reduce都是高阶函数。

我们再对高阶组件进行如下的解析:

  • 首先, 高阶组件 本身不是一个组件,而是一个函数
  • 其次,这个函数的***参数是一个组件***,返回值也是一个组件

高阶组件的调用过程类似于这样:

const EnhancedComponent = higherOrderComponent(WrappedComponent);

高阶函数的编写过程类似于这样:

function higherOrderComponent(WrapperComponent) {
  return class NewComponent extends PureComponent {
    render() {
      return <WrapperComponent/>
    }
  }
}

在ES6中,类表达式中类名是可以省略的,所以可以可以写成下面的写法:

function higherOrderComponent(WrapperComponent) {
  return class extends PureComponent {
    render() {
      return <WrapperComponent/>
    }
  }
}

另外,组件的名称都可以通过displayName来修改:

img修改名称

完整的代码,我们可以这样来编写:

import React, { PureComponent } from 'react';

function higherOrderComponent(WrapperComponent) {
  return class NewComponent extends PureComponent {
    render() {
      return <WrapperComponent/>
    }
  }
}

class App extends PureComponent {
  render() {
    return (
      <div>
        App
      </div>
    )
  }
}

export default higherOrderComponent(App);

高阶组件并不是React API的一部分,它是基于React的组合特性而形成的设计模式

高阶组件在一些React第三方库中非常常见:

  • 比如redux中的connect;
  • 比如react-router中的withRouter;

# 【高阶组件的使用】

# 1】组件props属性的增强

不修改原有代码的情况下,添加新的props

假如我们有如下案例:

class Header extends PureComponent {
  render() {
    const { name, age } = this.props;
    return <h2>Header {name + age}</h2>
  }
}

export default class App extends PureComponent {
  render() {
    return (
      <div>
        <Header name="aaa" age={18} />
      </div>
    )
  }
}

我们可以通过一个高阶组件,让使用者在不破坏原有结构的情况下对某个组件增强props:

function enhanceProps(WrapperCpn, otherProps) {
 return props => <WrapperCpn {...props} {...otherProps} />
}

const EnhanceHeader = enhanceProps(Header, {height: 1.88})

有没有感觉和数组的map方法很类似呀!!哈哈哈

# 2】利用高阶组件来共享Context

# 例子:

<body>
    <!-- 创建dom根节点 -->
    <div id="app"></div>

    <script type="text/babel">

        //1.创建context实例对象globalContext
        const globalContext = React.createContext()

        

        // react context 有提供者和消费者的概念来创建的

        class Father extends React.Component {

            render() {
                return (
                    <div className="Father">
                        <div>我是Father</div>

                        <globalContext.Consumer>
                            {value => value.a
                                //3.2方式二:使用Consumer的api标签包裹然后里面使用回到函数进行拿提供者提供的状态
                            }
                        </globalContext.Consumer>
                        <Son />
                    </div>
                )
            }

        }

        class Son extends React.Component {
            render() {
                return (
                    <div className="Son">
                        我是Son{this.context.a}
                        <button onClick={() => {
                            this.context.a = 'hhh '
                        }}></button>
                    </div>
                )
            }
        }
        //3.1方式一使用每个react组件对象内置的contextType等于创建的context实例,就可以直接在子组件里面this.context使用提供者提供的状态
        Son.contextType = globalContext


        class App extends React.Component {
            state = {
                a: '提供者提供的的状态'
            }
            render() {
                return (
                    //2.使用Context对象的提供者包裹根组件,里面必传一个属性,是公共仓库,所以组件拿状态都是这里拿
                    <globalContext.Provider value={this.state}>
                        <div className="App">
                            <div>我是App</div>
                            <Father />
                        </div>
                    </globalContext.Provider>
                )
            }
        }
        ReactDOM.render(<App />, document.getElementById("app"))
    </script>
</body>

在这里插入图片描述

# API

# React.createContext
const MyContext = React.createContext(defaultValue);

创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。

只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。这有助于在不使用 Provider 包装组件的情况下对组件进行测试。注意:将 undefined 传递给 Provider 的 value 时,消费组件的 defaultValue 不会生效。

# Context.Provider
<MyContext.Provider value={/* 某个值 */}>

每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。

Provider 接收一个 value 属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。

当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。

通过新旧值检测来确定变化,使用了与 Object.is 相同的算法。

注意

当传递对象给 value 时,检测变化的方式会导致一些问题:详见注意事项

# Class.contextType
class MyClass extends React.Component {
  componentDidMount() {
    let value = this.context;
    /* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */
  }
  componentDidUpdate() {
    let value = this.context;
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context;
    /* ... */
  }
  render() {
    let value = this.context;
    /* 基于 MyContext 组件的值进行渲染 */
  }
}
MyClass.contextType = MyContext;

挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象。这能让你使用 this.context 来消费最近 Context 上的那个值。你可以在任何生命周期中访问到它,包括 render 函数中。

注意:

你只通过该 API 订阅单一 context。如果你想订阅多个,阅读使用多个 Context 章节

如果你正在使用实验性的 public class fields 语法,你可以使用 static 这个类属性来初始化你的 contextType

class MyClass extends React.Component {
  static contextType = MyContext;
  render() {
    let value = this.context;
    /* 基于这个值进行渲染工作 */
  }
}
# Context.Consumer
<MyContext.Consumer>
  {value => /* 基于 context 值进行渲染*/}
</MyContext.Consumer>

一个 React 组件可以订阅 context 的变更,这让你在函数式组件中可以订阅 context。

这种方法需要一个函数作为子元素(function as a child)。这个函数接收当前的 context 值,并返回一个 React 节点。传递给函数的 value 值等等价于组件树上方离这个 context 最近的 Provider 提供的 value 值。如果没有对应的 Provider,value 参数等同于传递给 createContext()defaultValue

注意

想要了解更多关于 “函数作为子元素(function as a child)” 模式,详见 render props

# Context.displayName

context 对象接受一个名为 displayName 的 property,类型为字符串。React DevTools 使用该字符串来确定 context 要显示的内容。

示例,下述组件在 DevTools 中将显示为 MyDisplayName:

const MyContext = React.createContext(/* some value */);
MyContext.displayName = 'MyDisplayName';
<MyContext.Provider> // "MyDisplayName.Provider" 在 DevTools 中
<MyContext.Consumer> // "MyDisplayName.Consumer" 在 DevTools 中
Last Updated: 8/5/2020, 5:39:29 PM