高級函數是以函數爲參數,而且返回也是函數的的函數。相似的,高階組件(簡稱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調用父類組件方法而致使邏輯丟失。所以,在使用高階組件時,應儘可能經過代理方式實現高階組件。
願你成爲終身學習者
想了解更多生活鮮爲人知的一面,能夠關注個人大遷世界噢