前言
Higher-Order function(高阶函数)大家很熟悉,在函数式编程中的一个基本概念,它描述了这样一种函数:这种函数接受函数作为输出,或者输出一个函数。比如常用的工具方法reduce,map等都是高阶函数现在我们都知道高阶函数是什么,Higher-Ordercomponents(高阶组件)其实也是类似于高阶函数,它接受一个React组件作为输入,输出一个新的React组件
Concretely, a higher-order component is a function that takes a component and returns a new component.
通俗的语言解释:当我们用一个容器(w)把React组件包裹,高阶组件会返回一个增强(E)的组件。高阶组件让我们的代码更具有复用性,逻辑性与抽象特。它可以对props和state进行控制,也可以对render方法进行劫持...
大概是这样:
const EnhancedComponent = higherOrderComponent(WrappedComponent)
简单例子:
import React, { Component } from 'react';
import ExampleHoc from './example-hoc';
class UseContent extends Component {
render() {
console.log('props:',this.props);
return (
<div>
{this.props.title} - {this.props.name}
</div>
)
}
}
export default ExampleHoc(UseContent)
复制代码
import React, { Component } from 'react';
const ExampleHoc = WrappedComponent => {
return class extends Component {
constructor(props) {
super(props)
this.state = {
title: 'hoc-component',
name: 'arcsin1',
}
}
render() {
const newProps = {
...this.state,
}
return <WrappedComponent {...this.props} {...this.newProps} />
}
}
}
export default ExampleHoc
复制代码
组件UseContent,你可以看到其实是一个很简单的一个渲染而已,而组件ExampleHoc对它进行了增强,很简单的功能.
应用场景
以下代码我会用装饰器(decorator)书写
属性代理。 高阶组件通过被包裹的React组件来操作props
反向继承。 高阶组件继承于被包裹的React组件
1. 属性代理
小列子说明:
import React, { Component } from 'react'
import ExampleHoc from './example-hoc'
@ExampleHoc
export default class UseContent extends Component {
render() {
console.log('props:',this.props);
return (
<div>
{...this.props} //这里只是演示
</div>
)
}
}
复制代码
import React, { Component } from 'react'
const ExampleHoc = WrappedComponent => {
return class extends Component {
render() {
return <WrappedComponent {...this.props} />
}
}
}
export default ExampleHoc
复制代码
这样的组件就可以作为参数被调用,原始组件就具备了高阶组件对它的修饰。就这么简单,保持单个组件封装性的同时还保留了易用性。当然上述的生命周期如下:
didmount -> HOC didmount ->(HOCs didmount) ->(HOCs willunmount)-> HOC willunmount -> unmount
-
控制props
我可以读取,编辑,增加,移除从WrappedComponent传来的props,但是需要小心编辑和移除props。我们应该对高阶组件的props作新的命名防止混淆了。
例如:
import React, { Component } from 'react'
const ExampleHoc = WrappedComponent => {
return class extends Component {
render() {
const newProps = {
name: newText,
}
return <WrappedComponent {...this.props} {...newProps}/>
}
}
}
export default ExampleHoc
复制代码
-
通过refs使用引用
在高阶组件中,我们可以接受refs使用WrappedComponent的引用。 例如:
import React, { Component } from 'react'
const ExampleHoc = WrappedComponent => {
return class extends Component {
proc = wrappedComponentInstance => {
wrappedComponentInstance.method()
}
render() {
const newProps = Object.assign({}, this.props,{
ref: this.proc,
})
return <WrappedComponent {...this.newProps} />
}
}
}
export default ExampleHoc
复制代码
当WrappedComponent被渲染的时候,refs回调函数就会被执行,这样就会拿到一份WrappedComponent的实例的引用。这样就可以方便地用于读取和增加实例props,并调用实例。
- 抽象state
我们可以通过WrappedComponent提供props和回调函数抽象state。就像我们开始的例子,我们可以把原组件抽象为展示型组件,分离内部状态,搞成无状态组件。
例子:
import React, { Component } from 'react';
const ExampleHoc = WrappedComponent => {
return class extends Component {
constructor(props) {
super(props)
this.state = {
name: '',
}
}
onNameChange = e => {
this.setState({
name: e.target.value,
})
}
render() {
const newProps = {
name: {
value: this.state.name,
onChange: this.onNameChange,
}
}
return <WrappedComponent {...this.props} {...newProps} />
}
}
}
export default ExampleHoc
复制代码
在上面 我们通过把input的name prop和onchange方法提到了高阶组件中,这样有效的抽象了同样的state操作。
用法:
import React, { Component } from 'react'
import ExampleHoc from './example-hoc'
@ExampleHoc
export default class UseContent extends Component {
render() {
console.log('props:',this.props);
return (
<input name="name" {this.props.name} />
)
}
}
这样就是一个受控组件
复制代码
-
其它元素包裹WrappedComponent
其它,我们可以使用其它元素包裹WrappedComponent,这样既可以增加样式,也可以方便布局。例如
import React, { Component } from 'react'
const ExampleHoc = WrappedComponent => {
return class extends Component {
render() {
return (
<div style={{display: 'flex'}}>
<WrappedComponent {...this.props} />
</div>
)
}
}
}
export default ExampleHoc
复制代码
2. 反向继承
从字面意思,可以看出它与继承相关,先看看例子:
const ExampleHoc = WrappedComponent => {
return class extends WrappedComponent {
render() {
return super.render()
}
}
}
复制代码
正如看见的,高阶组件返回的组件继承与WrappedComponent,因为被动继承了WrappedComponent,所有的调用都是反向。所以这就是反代继承的由来。 这种方法与属性代理不太一样,它通过继承WrappedComponent来实现,方法可以通过super来顺序调用,来看看生命周期:
didmount -> HOC didmount ->(HOCs didmount) -> willunmount -> HOC willunmount ->(HOCs willunmount)
在反向继承中,高阶函数可以使用WrappedComponent的引用,这意味着可以使用WrappedComponent的state,props,生命周期和render方法。但它并不能保证完整的子组件树被解析,得注意。
- 渲染劫持
渲染劫持就是高阶组件可以控制WrappedComponent的渲染过程,并渲染各种各样的结果。我们可以在这个过程中任何React元素的结果中读取,增加,修改,删除props,或者修改React的元素树,又或者用样式控制包裹这个React元素树。
因为前面说了它并不能保证完整的子组件树被解析,有个说法:我们可以操控WrappedComponent元素树,并输出正确结果,但如果元素树种包含了函数类型的React组件,就不能操作组件的子组件。
先看看有条件的渲染例子:
const ExampleHoc = WrappedComponent => {
return class extends WrappedComponent {
render() {
if(this.props.loggedIn) { //当登录了就渲染
return super.render()
} else {
return null
}
}
}
}
复制代码
对render输出结果的修改:
const ExampleHoc = WrappedComponent => {
return class extends WrappedComponent {
render() {
const eleTree = super.render()
let newProps = {}
if(eleTree && eleTree.type === 'input') {
newProps = {value: '这不能被渲染'}
}
const props = Object.assgin({},eleTree.props,newProps)
const newEleTree = React.cloneElement(eleTree, props, eleTree.props.children)
return newEleTree
}
}
}
复制代码
-
控制state
高阶组件是可以读取,修改,删除WrappedComponent实例的state,如果需要的话,也可以增加state,但这样你的WrappedComponent会变得一团糟。因此大部分的高阶组件多都应该限制读取或者增加state,尤其是增加state,可以通过重新命名state,以防止混淆。
看看例子:
const ExampleHoc = WrappedComponent => {
return class extends WrappedComponent {
render() {
<div>
<h3>HOC debugger</h3>
<p>Props <pre>{JSON.stringfy(this.props,null,1)}</pre></p>
<p>State <pre>{JSON.stringfy(this.state,null,2)}</pre></p>
{super.render()}
</div>
}
}
}
复制代码
高阶组件接受其它参数
举个列子,我把用户信息存在本地LocalStorage中,当然里面有很多key,但是我不需要用到所有,我希望按照我的喜好得到我自己想要的。
import React, { Component } from 'react'
const ExampleHoc = (key) => (WrappedComponent) => {
return class extends Component {
componentWillMount() {
let data = localStorage.getItem(key);
this.setState({data});
}
render() {
return <WrappedComponent data={this.state.data} {...this.props} />
}
}
}
复制代码
import React, { Component } from 'react'
class MyComponent2 extends Component {
render() {
return <div>{this.props.data}</div>
}
}
const MyComponent2WithHOC = ExampleHoc(MyComponent2, 'data')
export default MyComponent2WithHOC
复制代码
import React, { Component } from 'react'
class MyComponent3 extends Component {
render() {
return <div>{this.props.data}</div>
}
}
const MyComponent3WithHOC = ExampleHoc(MyComponent3, 'name')
export default MyComponent3WithHOC
复制代码
实际上,此时的ExampleHoc和我们最初对高阶组件的定义已经不同。它已经变成了一个高阶函数,但这个高阶函数的返回值是一个高阶组件。我们可以把它看成高阶组件的变种形式。这种形式的高阶组件大量出现在第三方库中。如react-redux中的connect就是一个典型。请去查看react-redux的api就可以知道了。
有问题望指出,谢谢!
参考:
-
Higher-Order Components: higher-order-components
-
React Higher Order Components in depth: React Higher Order Components in depth