前言
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