高级函数是以函数为参数,而且返回也是函数的的函数。相似的,高阶组件(简称HOC)接收 React 组件为参数,而且返回一个新的React组件。高阶组件本质也是一个函数,并非一个组件。高阶组件的函数形式以下:react
const EnhanceComponent = higherOrderComponent(WrappedComponent)
经过一个简单的例子解释高阶组件是如何复用的。如今有一个组件MyComponent,须要从LocalStorage中获取数据,而后渲染到界面。通常状况下,咱们能够这样实现:app
import React, { Component } from 'react' class MyComponent extends Component { componentWillMount() { let data = localStorage.getItem('data'); this.setState({data}); } render() { return( <div>{this.state.data}</div> ) } }
代码很简单,但当其它组件也须要从LocalStorage 中获取一样的数据展现出来时,每一个组件都须要重写一次 componentWillMount 中的代码,这显然是很冗余的。下面让我人来看看使用高阶组件改写这部分代码。函数
import React, { Component } from 'react' function withPersistentData(WrappedComponent) { return class extends Component { componentWillMount() { let data = localStorage.getItem('data'); this.setState({data}); } render() { // 经过{ ...this.props} 把传递给当前组件属性继续传递给被包装的组件 return <WrappedComponent data={this.state.data} {...this.props}/> } } } class MyComponent extends Component{ render() { return <div>{this.props.data}</div> } } const MyComponentWithPersistentData = withPersistentData(MyComponent);
withPersistentData 就是一个高阶组件,它返回一个新的组件,在新组件中 componentWillMount 中统一处理从 LocalStorage 中获取数据逻辑,而后将获取到的数据经过 props 传递给被包装的组件 WrappedComponent,这样在WrappedComponent中就能够直接使用 this.props.data 获取须要展现的数据,当有其余的组件也须要这段逻辑时,继续使用 withPersistentData 这个高阶组件包装这些组件。学习
高阶组件的使用场景主要有如下4中:
1)操纵 props
2) 经过 ref 访问组件实例
3) 组件状态提高
4)用其余元素包装组件this
在被包装组件接收 props 前, 高阶组件能够先拦截到 props, 对 props 执行增长、删除或修改的操做,而后将处理后的 props 再传递被包装组件,一中的例子就是属于这种状况。spa
高阶组件 ref 获取被包装组件实例的引用,而后高阶组件就具有了直接操做被包装组件的属性或方法的能力。代理
import React, { Component } from 'react' function withRef(wrappedComponent) { return class extends Component{ constructor(props) { super(props); this.someMethod = this.someMethod.bind(this); } someMethod() { this.wrappedInstance.comeMethodInWrappedComponent(); } render() { // 为被包装组件添加 ref 属性,从而获取组件实例并赋值给 this.wrappedInstance return <wrappedComponent ref={(instance) => { this.wrappedInstance = instance }} {...this.props}/> } } }
当 wrappedComponent 被渲染时,执行 ref 的回调函数,高阶组件经过 this.wrappedInstance 保存 wrappedComponent 实例引用,在 someMethod 中经过 this.wrappedInstance 调用 wrappedComponent 中的方法。这种用法在实际项目中不多会被用到,但当高阶组件封装的复用逻辑须要被包装组件的方法或属性的协同支持时,这种用法就有了用武之地。code
高阶组件能够经过将被包装组件的状态及相应的状态处理方法提高到高阶组件自身内部实现被包装组件的无状态化。一个典型的场景是,利用高阶组件将本来受控组件须要本身维护的状态统一提高到高阶组件中。component
import React, { Component } from 'react' function withRef(wrappedComponent) { return class extends Component{ constructor(props) { super(props); this.state = { value: '' } this.handleValueChange = this.handleValueChange.bind(this); } handleValueChange(event) { this.this.setState({ value: event.EventTarget.value }) } render() { // newProps保存受控组件须要使用的属性和事件处理函数 const newProps = { controlledProps: { value: this.state.value, onChange: this.handleValueChange } } return <wrappedComponent {...this.props} {...newProps}/> } } }
这个例子把受控组件 value 属性用到的状态和处理 value 变化的回调函数都提高到高阶组件中,当咱们再使用受控组件时,就能够这样使用:继承
import React, { Component } from 'react' function withControlledState(wrappedComponent) { return class extends Component{ constructor(props) { super(props); this.state = { value: '' } this.handleValueChange = this.handleValueChange.bind(this); } handleValueChange(event) { this.this.setState({ value: event.EventTarget.value }) } render() { // newProps保存受控组件须要使用的属性和事件处理函数 const newProps = { controlledProps: { value: this.state.value, onChange: this.handleValueChange } } return <wrappedComponent {...this.props} {...newProps}/> } } } class SimpleControlledComponent extends React.Component { render() { // 此时的 SimpleControlledComponent 为无状态组件,状态由高阶组件维护 return <input name="simple" {...this.props.controlledProps}/> } } const ComponentWithControlledState = withControlledState(SimpleControlledComponent);
高阶组件的参数并不是只能是一个组件,它还能够接收其余参数。例如一中是从 LocalStorage 中获取 key 为 data的数据,当须要获取数据的 key不肯定时,withPersistentData 这个高阶组件就不知足须要了。咱们可让它接收一个额外参数来决定从 LocalStorage 中获取哪一个数据:
import React, { Component } from 'react' function withPersistentData(WrappedComponent, key) { return class extends Component { componentWillMount() { let data = localStorage.getItem(key); this.setState({ data }); } render() { // 经过{ ...this.props} 把传递给当前组件属性继续传递给被包装的组件 return <WrappedComponent data={this.state.data} {...this.props} /> } } } class MyComponent extends Component { render() { return <div>{this.props.data}</div> } } // 获取 key='data' 的数据 const MyComponent1WithPersistentData = withPersistentData(MyComponent, 'data'); // 获取 key='name' 的数据 const MyComponent2WithPersistentData = withPersistentData(MyComponent, 'name');
新版本的 withPersistentData 知足获取不一样 key 值的需求,但实际状况中,咱们不多使用这种方式传递参数,而是采用更加灵活、更具能用性的函数形式:
HOC(...params)(WrappedComponent)
HOC(...params) 的返回值是一个高阶组件,高阶组件须要的参数是先传递 HOC 函数的。用这种形式改写 withPersistentData 以下(注意:这种形式的高阶组件使用箭头函数定义更为简洁):
import React, { Component } from 'react' const withPersistentData = (key) => (WrappedComponent) => { return class extends Component { componentWillMount() { let data = localStorage.getItem(key); this.setState({ data }); } render() { // 经过{ ...this.props} 把传递给当前组件属性继续传递给被包装的组件 return <WrappedComponent data={this.state.data} {...this.props} /> } } } class MyComponent extends Component { render() { return <div>{this.props.data}</div> } } // 获取 key='data' 的数据 const MyComponent1WithPersistentData = withPersistentData('data')(MyComponent); // 获取 key='name' 的数据 const MyComponent2WithPersistentData = withPersistentData('name')(MyComponent);
前面介绍的高阶组件的实现方式都是由高阶组件处理通用逻辑,而后将相关属性传递给被包装组件,咱们称这种方式为属性代理。除了属性代理外,还能够经过继承方式实现高阶组件:经过 继承被包装组件实现逻辑的复用。继承方式实现的高阶组件经常使用于渲染劫持。例如,当用户处于登陆状态时,容许组件渲染,不然渲染一个空组件。代码以下:
function withAuth(WrappedComponent) { return class extends WrappedComponent { render() { if (this.props.loggedIn) { return super.render(); } else { return null; } } } }
根据 WrappedComponent的 this.props.loggedIn 判读用户是否已经登陆,若是登陆,就经过 super.render()调用 WrappedComponent 的 render 方法正常渲染组件,不然返回一个 null, 继承方式实现高阶组件对被包装组件具备侵入性,当组合多个高阶使用时,很容易由于子类组件忘记经过 super调用父类组件方法而致使逻辑丢失。所以,在使用高阶组件时,应尽可能经过代理方式实现高阶组件。
愿你成为终身学习者
想了解更多生活鲜为人知的一面,能够关注个人大迁世界噢