博客地址:http://www.luckyjing.com/post...javascript
在開始講述高階組件前,咱們先來回顧高階函數的定義:接收函數做爲輸入,或者輸出另外一個函數的一類函數,被稱做高階函數。對於高階組件,它描述的即是接受React組件做爲輸入,輸出一個新的React組件的組件。html
更通俗地描述爲,高階組件經過包裹(wrapped)被傳入的React組件,通過一系列處理,最終返回一個相對加強(enhanced)的React組件,供其餘組件調用。java
下面咱們來實現一個最簡單的高階組件(函數),它接受一個React組件,包裹後而後返回。node
export default function withHeader(WrappedComponent) { return class HOC extends Component { render() { return <div> <div className="demo-header"> 我是標題 </div> <WrappedComponent {...this.props}/> </div> } } }
在其餘組件裏,咱們引用這個高階組件,用來強化它。react
@withHeader export default class Demo extends Component { render() { return ( <div> 我是一個普通組件 </div> ); } }
在這裏使用了ES7
裏的decorator
,來提高寫法上的優雅,可是實際上它只是一個語法糖,下面這種寫法也是能夠的。git
const EnhanceDemo = withHeader(Demo);
隨後,觀察React組件樹發生了什麼變化,如圖所示,能夠發現Demo組件被HOC組件包裹起來了,符合了高階組件的預期,即組件是層層包裹起來的,如同洋蔥同樣。github
可是隨之帶來的問題是,若是這個高階組件被使用了屢次,那麼在調試的時候,將會看到一大堆HOC
,因此這個時候須要作一點小優化,就是在高階組件包裹後,應當保留其原有名稱。編程
咱們改寫一下上述的高階組件代碼,增長了getDisplayName
函數以及靜態屬性displayName
,此時再去觀察DOM Tree
。antd
function getDisplayName(component) { return component.displayName || component.name || 'Component'; } export default function (WrappedComponent) { return class HOC extends Component { static displayName = `HOC(${getDisplayName(WrappedComponent)})` render() { return <div> <div className="demo-header"> 我是標題 </div> <WrappedComponent {...this.props}/> </div> } } }
此時,本來組件的名稱正確地顯示在了DOM Tree
上。app
這個簡單的例子裏高階組件只作了一件事,那即是爲被包裹的組件添加一個標題樣式。這個高階組件能夠用到任何一個須要添加此邏輯的組件上,只須要被此高階組件修飾便可。
由此能夠看出,高階組件的主要功能是封裝並抽離組件的通用邏輯,讓此部分邏輯在組件間更好地被複用。
仍是以上述例子爲例,此高階組件僅僅只是展現了我是標題這個名稱,可是爲了更好的抽象,此標題應當能夠被參數化,以下方式調用。
// 若是傳入參數,則傳入的參數將做爲組件的標題呈現 @withHeader('Demo') export default class Demo extends Component { render() { return ( //... ); } }
withHeader
須要被改寫成以下形式,它接受一個參數,而後返回一個高階組件(函數)。
export default function (title) { return function (WrappedComponent) { return class HOC extends Component { render() { return <div> <div className="demo-header"> {title ? title : '我是標題'} </div> <WrappedComponent {...this.props}/> </div> } } } }
使用ES6寫法能夠更加簡潔。
export default(title) => (WrappedComponent) => class HOC extends Component { render() { return <div> <div className="demo-header"> {title ? title : '我是標題'} </div> <WrappedComponent {...this.props}/> </div> } }
如圖能夠看到,傳入的參數已經反映在DOM Tree
裏了。
柯里化 Curry
概念:只傳遞函數的一部分參數來調用它,讓它返回一個函數去處理剩下的參數。
函數簽名:fun(params)(otherParams)
應用:在React裏,經過柯里化,咱們能夠經過傳入不一樣的參數來獲得不一樣的高階組件。
屬性代理是最多見的高階組件的使用方式,上述描述的高階組件就是這種方式。它經過作一些操做,將被包裹組件的props
和新生成的props
一塊兒傳遞給此組件,這稱之爲屬性代理。
export default function withHeader(WrappedComponent) { return class HOC extends Component { render() { const newProps = { test:'hoc' } // 透傳props,而且傳遞新的newProps return <div> <WrappedComponent {...this.props} {...newProps}/> </div> } } }
這種方式返回的React組件繼承了被傳入的組件,因此它可以訪問到的區域、權限更多,相比屬性代理方式,它更像打入組織內部,對其進行修改。具體的能夠參考附錄裏提供的連接進行深刻學習。
export default function (WrappedComponent) { return class Inheritance extends WrappedComponent { componentDidMount() { // 能夠方便地獲得state,作一些更深刻的修改。 console.log(this.state); } render() { return super.render(); } } }
上述高階組件爲React組件加強了一個功能,若是須要同時增長多個功能須要怎麼作?這種場景很是常見,例如我既須要增長一個組件標題,又須要在此組件未加載完成時顯示Loading。
@withHeader @withLoading class Demo extends Component{ }
使用compose
能夠簡化上述過程,也能體現函數式編程的思想。
const enhance = compose(withHeader,withLoading); @enhance class Demo extends Component{ }
組合
Compose
compose
能夠幫助咱們組合任意個(包括0個)高階函數,例如compose(a,b,c)
返回一個新的函數d
,函數d
依然接受一個函數做爲入參,只不過在內部會依次調用c,b,a
,從表現層對使用者保持透明。基於這個特性,咱們即可以很是便捷地爲某個組件加強或減弱其特徵,只須要去變動compose函數裏的參數個數即可。
compose
函數實現方式有不少種,這裏推薦其中一個recompact.compose
,詳情見下方參考類庫。
實現Loading組件時,發現須要去攔截它的渲染過程,故使用了反向繼承的方式來完成。
在經過裝飾器調用時,須要傳入一個函數做爲入參,函數能夠獲取到props
,隨後返回一個Boolean
對象,來決定組件是否須要顯示Loading
態
import React, {Component} from 'react'; import {Spin} from 'antd'; export default function (loadingCheck) { return function (WrappedComponent) { return class extends WrappedComponent { componentWillUpdate(nextProps, nextState) { console.log('withLoading將會更新'); } render() { if (loadingCheck(this.props)) { return <Spin tip="加載中" size="large"> {super.render()} </Spin> } else { return super.render(); } } } } } // 使用 @withLoading(props => { return props.IndexStore.accountList.length == 0; })
實現copy
組件的時候,咱們發現不須要去改變組件內部的展現方式,只是爲其在外圍增長一個功能,並不會侵入被傳入的組件,故使用了屬性代理的方式。
import gotem from 'gotem'; import React, {Component} from 'react'; import ReactDom from 'react-dom'; import {message} from 'antd'; export default copy = (targetName) => { return (WrappedComponent) => { return class extends Component { componentDidMount() { const ctx = this; const dom = ReactDom.findDOMNode(ctx); const nodes = { trigger: dom, // targetName爲DOM選擇器,複製組件將會複製它的值 target: dom.querySelector(targetName) }; gotem(nodes.trigger, nodes.target, { success: function () { message.success('複製成功'); }, error: function () { message.error('複製失敗,請手動輸入'); } }); } render() { return <WrappedComponent {...this.props}/>; } }; }; } // 使用 // 傳入 h3 ,讓複製組件去獲取它的值 @copy('h3') class Info extends Component { render() { return ( <div> <h3> 阿里雲,點擊複製這段文字 </h3> </div> ); } }
高階組件做爲一個函數,它能夠更加純粹地關注業務邏輯層面的代碼,好比數據處理,數據校驗,發送請求等,能夠改善目前代碼裏業務邏輯和UI邏輯混雜在一塊兒的現狀。父組件則是UI層的東西,咱們先前常常把一些業務邏輯處理放在父組件裏,這樣會形成父組件混亂的狀況。爲了代碼進一步解耦,能夠考慮使用高階組件這種模式。
recompact
提供了一系列使用的高階組件,能夠加強組件的行爲,能夠利用此庫學習高階組件的寫法。
import recompact from 'recompact' import { pure, withProps } from 'recompact' const enhance = recompact.compose( withProps({ className: 'beautiful' }), pure, ) @enhance class Demo extends Component{ }
經過使用此庫提供的高階組件,能夠方便地讓列表元素可拖動。
高階組件是Decorator
模式在React
的一種實現,它能夠抽離公共邏輯,像洋蔥同樣層層疊加給組件,每一層職能分明,能夠方便地抽離與增添。在優化代碼或解耦組件時,能夠考慮使用高階組件模式。