高階元件類似於高階函式,它接受React元件作為輸入,輸出一個新的React元件。當React元件被包裹時,高階元件會返回一個增強的React元件,高階元件可以提升程式碼的複用性、邏輯性、抽象性。
實現高階元件有兩種方式。
- 屬性代理
- 反向繼承 本篇文章主要分析屬性代理的幾種實現方式。
1. 屬性代理
通過高階元件來傳遞props,這種方法就是屬性代理。下面對屬性代理的幾個功能進行講解。
- 控制props
我們可以讀取、增加、編輯或是移除從
WrappedComponent
傳進來的props
但需要小心刪除與編輯重要的props,應該儘可能對高階元件的props做新的命名以防止混淆。
例如增加一個新的prop:
import React, { Component } from 'react';
const Mycontainer = (WrappedComponent) =>
class extends Component {
render () {
const newProps = {
text: 'newText'
}
return <WrappedComponent {...this.props} {...newProps}/>
}
}
export default Mycontainer
複製程式碼
當呼叫高階元件事,可以用
text
這個新的props
了。對於原元件來說,只要套用這個高階元件,新元件就會多一個屬性。
import React, { Component } from 'react'
import Mycontainer from 'container/Mycontainer'
class Page1 extends Component {
render() {
console.log("this.props)
return <div>測試新新增一個屬性</div>
}
}
export default Mycontainer(Page1)
複製程式碼
- 通過refs使用引用
在高階元件中,我們可以接受
refs
使用WrappedComponent
的引用。例如:
import React, { Component } from 'react';
const Mycontainer = (WrappedComponent) =>
class extends Component {
proc(wrappedComponentInstance) {
console.log("ref回撥函式被執行", wrappedComponentInstance)
console.log(wrappedComponentInstance.props);
wrappedComponentInstance.newText = "通過refs新增屬性"
}
render () {
const props = Object.assign({}, this.props, {
ref: this.proc.bind(this),
});
return <WrappedComponent {...props} />
}
}
export default Mycontainer
複製程式碼
- 抽象state
import React, { Component } from 'react';
const Mycontainer = (WrappedComponent) =>
class extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
}
}
onNameChange = (event) => {
this.setState({
name: event.target.value,
})
}
render () {
const newProps = {
name: {
value: this.state.name,
onChange: this.onNameChange,
}
}
return <WrappedComponent {...this.props} {...newProps}/>
}
}
export default Mycontainer
複製程式碼
- 使用其他元素包裹
WrappedComponent
,這既可以為了增加樣式,也可以是為了佈局。
import React, { Component } from 'react';
const Mycontainer = (WrappedComponent) =>
class extends Component {
render () {
const newProps = {
text: 'newText'
}
return (
<div style={{display:"block"}}>
<WrappedComponent {...this.prop} />
</div>
)
}
}
export default Mycontainer
複製程式碼
2. 反向繼承
另一種高階元件的實現方式成為反向繼承,一個簡單的實現
const MyContainer = (WrappedComponent) => {
class extends WrappedComponent {
render() {
return super.render();
}
}
}
複製程式碼
正如所見,高階元件返回的元件繼承與WrappedComponent。因為被動的繼承了WrappedComponent,所有的呼叫都會反向。 這種方法與屬性代理不太一樣。他通過繼承WrappedComponent來實現,方法可以通過super來順序呼叫,反向繼承不能保證完整的子樹被解析。 反向繼承有兩個比較大的特點。
- 渲染劫持
渲染劫持指的是高階元件可以控制WrappedComponent的渲染過程,並渲染各種各樣的結果。我們可以在這個過程中在任何React元素輸出的結果中讀取、增加、修改、刪除props,或讀取或修改React樹,或條件顯示樹,又或是用樣式控制包裹樹。
- 一個條件渲染的示例:
const MyContainer = (WrappedComponent) => {
class extends WrappedComponent {
render() {
if(this.props.loggedIn) {
return super.render();
} else {
return null;
}
}
}
}
複製程式碼
- 我們可以對render的輸出結果進行修改
const MyContainer = (WrappedComponent) => {
class extends WrappedComponent {
render() {
const elementsTree = super.render();
let newProps = {};
if(elementsTree && elementsTree.type === 'input') {
newProps = {
value:'may the force be with you'
};
}
const props = Object.assign({},elementsTree.props,newProps);
const newElementsTree = React.cloneElement(elementsTree,props,elementsTree.props.children);
return newElementsTree;
}
}
}
複製程式碼
- 控制state
高階元件可以讀取、修改或刪除WrappedComponent例項中的state,如果需要的話,也可以增加state.但這樣做,可能會讓WrappedComponent元件內部變得一團糟。大部分高階元件都應該限制讀取或增加state,尤其是後者,可以通過重新命名state,以防止混淆。
const MyContainer = (WrappedComponent) => {
class extends WrappedComponent {
render() {
<div>
<h2>HOC Debugger Component</h2>
<p>props</p><pre>{JSON.stringify(this.props,null,2)}</pre>
<p>state</p><pre>{JSON.stringify(this.state,null,2)}</pre>
</div>
}
}
}
複製程式碼
這個例子中,顯示了WrappedComponent的props和state,以方便我們在程式中區除錯他們。
3.元件命名
4.元件引數
有時,我們呼叫高階元件時需要傳入一些引數。
function HOCFactoryFactory(...params) {
return function HOCFactory(WrappedComponent) {
return class HOC extends Component {
render() {
return <WrappedComponent {...this.props}
}
}
}
}
複製程式碼