摘要:之所以稱之為高階,是因為在中,這種嵌套關系會反映到組件樹上,層層嵌套就好像高階函數的一樣,如圖從圖上也可以看出,組件樹雖然嵌套了多層,但是實際渲染的結構并沒有改變。你可能已經注意到,目前我寫的所有高階函數,都是形如表示為。
什么是高階組件
Higher-Order Components (HOCs) are JavaScript functions which add functionality to existing component classes.
通過函數向現有組件類添加邏輯,就是高階組件。
讓我們先來看一個可能是史上最無聊的高階組件:
function noId() { return function(Comp) { return class NoID extends Component { render() { const {id, ...others} = this.props; return () } } } } const WithoutID = noId()(Comp);
這個例子向我們展示了高階組件的工作方式:通過函數和閉包,改變已有組件的行為——這里是忽略id屬性——而完全不需要修改任何代碼。
之所以稱之為高階,是因為在React中,這種嵌套關系會反映到組件樹上,層層嵌套就好像高階函數的function in function一樣,如圖:
從圖上也可以看出,組件樹雖然嵌套了多層,但是實際渲染的DOM結構并沒有改變。
如果你對這點有疑問,不妨自己寫寫例子試下,加深對React的理解。現在可以先記下結論:我們可以放心的使用多層高階組件,甚至重復地調用,而不必擔心影響輸出的DOM結構。
借助函數的邏輯表現力,高階組件的用途幾乎是無窮無盡的:
適配器有的時候你需要替換一些已有組件,而新組件接收的參數和原組件并不完全一致。
你可以修改所有使用舊組件的代碼來保證傳入正確的參數——考慮改行吧如果你真這么想
也可以把新組件做一層封裝:
class ListAdapter extends Component { mapProps(props) { return {/* new props */} } render() { return} }
如果有十個組件需要適配呢?如果你不想照著上面寫十遍,或許高階組件可以給你答案
function mapProps(mapFn) { return function(Comp) { return class extends Component { render() { return} } } } const ListAdapter = mapProps(mapPropsForNewList)(NewList);
借助高階組件,關注點被分離得更加干凈:只需要關注真正重要的部分——屬性的mapping。
這個例子有些價值,卻仍然不夠打動人,如果你也這么想,請往下看:
處理副作用純組件易寫易測,越多越好,這是常識。然而在實際項目中,往往有許多的狀態和副作用需要處理,最常見的情況就是異步了。
假設我們需要異步加載一個用戶列表,通常的代碼可能是這樣的:
class UserList extends Component { constructor(props) { super(); this.state = { list: [] } } componentDidMount() { loadUsers() .then(data=> this.setState({list: data.userList}) ) } render() { return () } /* other bussiness logics */ }
實際情況中,以上代碼往往還會和其它一些業務函數混雜在一起——我們創建了一個業務與副作用混雜的、有狀態的組件。
如果再來一個書單列表呢?再寫一個BookList然后把loadUsers改成loadBooks ?
不僅代碼重復,大量有狀態和副作用的組件,也使得應用更加難以測試。
也許你會考慮使用Flux。它確實能讓你的代碼更清晰,但是在有些場景下使用Flux就像大炮打蚊子。比如一個異步的下拉選擇框,如果要考慮復用的話,傳統的Flux/Reflux幾乎無法優雅的處理,Redux稍好一些,但仍然很難做優雅。關于flux/redux的缺點不深入,有興趣的可以參考Cycle.js作者的文章
回到問題的本源:其實我們只想要一個能復用的異步下拉列表而已啊!
高階函數試試?
import React, { Component } from "react"; const DEFAULT_OPTIONS = { mapStateToProps: undefined, mapLoadingToProps: loading => ({ loading }), mapDataToProps: data => ({ data }), mapErrorToProps: error => ({ error }), }; export function connectPromise(options) { return (Comp) => { const finalOptions = { ...DEFAULT_OPTIONS, ...options, }; const { promiseLoader, mapLoadingToProps, mapStateToProps, mapDataToProps, mapErrorToProps, } = finalOptions; class AsyncComponent extends Component { constructor(props) { super(props); this.state = { loading: true, data: undefined, error: undefined, }; } componentDidMount() { promiseLoader(this.props) .then( data => this.setState({ data, loading: false }), error => this.setState({ error, loading: false }), ); } render() { const { data, error, loading } = this.state; const dataProps = data ? mapDataToProps(data) : undefined; const errorProps = error ? mapErrorToProps(error) : undefined; return (); } } return AsyncComponent; }; } const UserList = connectPromise({ promiseLoader: loadUsers, mapDataToProps: result=> ({list: result.userList}) })(List); //List can be a pure component const BookList = connectPromise({ promiseLoader: loadBooks, mapDataToProps: result=> ({list: result.bookList}) })(List);
不僅大大減少了重復代碼,還把散落各處的異步邏輯裝進了可以多帶帶管理和測試的籠子,在業務場景中,只需要純組件 + 配置 就能實現相同的功能——而無論是純組件還是配置,都是對單元測試友好的,至少比異步組件友好多了。
使用curry & compose高階組件的另一個亮點,就是對函數式編程的友好。你可能已經注意到,目前我寫的所有高階函數,都是形如:
config => { return Component=> { return HighOrderCompoent } }
表示為config=> Component=> Component。
寫成嵌套的函數是為了手動curry化,而參數的順序(為什么不是Component=> config=> Component),則是為了組合方便。關于curry與compose的使用,可以移步我的另一篇blog
舉個栗子,前面講了適配器和異步,我們可以很快就組合出兩者的結合體:使用NewList的異步用戶列表
UserList = compose( connectPromise({ promiseLoader: loadUsers, mapResultToProps: result=> ({list: result.userList}) }), mapProps(mapPropsForNewList) )(NewList);總結
在團隊內部分享里,我的總結是三個詞 Easy, Light-weight & Composable.
其實高階組件并不是什么新東西,本質上就是Decorator模式在React的一種實現,但在相當一段時間內,這個優秀的模式都被人忽略。在我看來,大部分使用mixin和class extends的地方,高階組件都是更好的方案——畢竟組合優于繼承,而mixin——個人覺得沒資格參與討論。
使用高階組件還有兩個好處:
適用范圍廣,它不需要es6或者其它需要編譯的特性,有函數的地方,就有HOC。
Debug友好,它能夠被React組件樹顯示,所以可以很清楚地知道有多少層,每層做了什么。相比之下無論是mixin還是繼承,都顯得非常隱晦。
值得慶幸的是,社區也明顯注意到了高階組件的價值,無論是大家非常熟悉的react-redux 的connect函數,還是redux-form,高階組件的應用開始隨處可見。
下次當你想寫mixin或class extends的時候,不妨也考慮下高階組件。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/78902.html
摘要:簡單來說高階組件就是一個函數,它接受一個組件作為參數然后返回一個新組件。主要用于組件之間邏輯復用。使用由于數據請求是異步的,為了不讓用戶看到一片空白,當數據請求還沒有返回時,展示組件。組合函數,提升代碼可閱讀性。 簡單來說高階組件(HOC)就是一個函數,它接受一個組件作為參數然后返回一個新組件。HOC 主要用于組件之間邏輯復用。比如你寫了幾個組件,他們之間的邏輯幾乎相同,就可以用 HOC 對...
簡介:簡單實現react-redux基礎api react-redux api回顧 把store放在context里,所有子組件可以直接拿到store數據 使組件層級中的 connect() 方法都能夠獲得 Redux store 根組件應該嵌套在 中 ReactDOM.render( , rootEl ) ReactDOM.render( ...
摘要:我們可以在組件的設計上,玩轉出很多花樣。但是,如何對一個功能復雜且臃腫的組件進行分解,也許并不是一件簡單的事情。同時,借助于新的算法引擎,兩個單元組件在渲染的效率上,樂觀地預計會有較大幅度的提升。 之前分享過幾篇關于React技術棧的文章: 做出Uber移動網頁版還不夠 極致性能打造才見真章 解析Twitter前端架構 學習復雜場景數據設計 React Conf 2017 干貨總結1...
摘要:因為工作中一直在使用,也一直以來想總結一下自己關于的一些知識經驗。于是把一些想法慢慢整理書寫下來,做成一本開源免費專業簡單的入門級別的小書,提供給社區。本書的后續可能會做成視頻版本,敬請期待。本作品采用署名禁止演繹國際許可協議進行許可 React.js 小書 本文作者:胡子大哈本文原文:React.js 小書 轉載請注明出處,保留原文鏈接以及作者信息 在線閱讀:http://huzi...
摘要:栗子的方法就是一個,他獲取,在中給添加需要的。本來準備把詳細代碼當個栗子貼出來的,結果突然想到公司保密協議,所以。。。栗子這樣子你就可以在父組件中這樣獲取的值了。 什么是HOC? HOC(全稱Higher-order component)是一種React的進階使用方法,主要還是為了便于組件的復用。HOC就是一個方法,獲取一個組件,返回一個更高級的組件。 什么時候使用HOC? 在Reac...
閱讀 3818·2023-04-25 19:07
閱讀 3563·2021-11-22 12:02
閱讀 3145·2021-10-12 10:11
閱讀 3922·2021-09-03 10:49
閱讀 2896·2019-08-30 13:21
閱讀 3011·2019-08-30 11:14
閱讀 2093·2019-08-29 15:40
閱讀 2880·2019-08-28 18:29