# 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来修改:
修改名称
完整的代码,我们可以这样来编写:
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 中