精讀《React 高階組件》

本期精讀文章是:React Higher Order Components in depth

1 引言

高階組件( higher-order component ,HOC )是 React 中復用組件邏輯的一種進階技巧。它本身并不是 React 的 API,而是一種 React 組件的設計理念,眾多的 React 庫已經證明了它的價值,例如耳熟能詳的 react-redux。

高階組件的概念其實并不難,我們能通過類比高階函數迅速掌握。高階函數是把函數作為參數傳入到函數中并返回一個新的函數。這里我們把函數替換為組件,就是高階組件了。

const EnhancedComponent = higherOrderComponent(WrappedComponent);

當然了解高階組件的概念只是萬里長征第一步,精讀文章在闡述其概念與實現外,也強調了其重要性與局限性,以及與其他方案的比較,讓我們一起來領略吧。

2 內容概要

高階組件常見有兩種實現方式,一種是 Props Proxy,它能夠對 WrappedComponent 的 props 進行操作,提取 WrappedComponent state 以及使用其他元素來包裹 WrappedComponent。Props Proxy 作為一層代理,具有隔離的作用,因此傳入 WrappedComponent 的 ref 將無法訪問到其本身,需要在 Props Proxy 內完成中轉,具體可參考以下代碼,react-redux 也是這樣實現的。

此外各個 Props Proxy 的默認名稱是相同的,需要根據 WrappedComponent 來進行不同命名。

function ppHOC(WrappedComponent) {return class PP extends React.Component {// 實現 HOC 不同的命名static displayName = `HOC(${WrappedComponent.displayName})`;getWrappedInstance() {return this.wrappedInstance;}// 實現 ref 的訪問setWrappedInstance(ref) {this.wrappedInstance = ref;}render() {return <WrappedComponent {...this.props,ref: this.setWrappedInstance.bind(this),} />}}
}@ppHOC
class Example extends React.Component {static displayName = 'Example';handleClick() { ... }...
}class App extends React.Component {handleClick() {this.refs.example.getWrappedInstance().handleClick();}render() {return (<div><button onClick={this.handleClick.bind(this)}>按鈕</button><Example ref="example" /></div>  );}
}

另一種是 Inheritance Inversion,HOC 類繼承了 WrappedComponent,意味著可以訪問到 WrappedComponent 的 state、props、生命周期和 render 等方法。如果在 HOC 中定義了與 WrappedComponent 同名方法,將會發生覆蓋,就必須手動通過 super 進行調用了。通過完全操作 WrappedComponent 的 render 方法返回的元素樹,可以真正實現渲染劫持。這種方案依然是繼承的思想,對于 WrappedComponent 也有較強的侵入性,因此并不常見。

function ppHOC(WrappedComponent) {return class ExampleEnhance extends WrappedComponent {...componentDidMount() {super.componentDidMount();}componentWillUnmount() {super.componentWillUnmount();}render() {...return super.render();}}
}

3 精讀

本次提出獨到觀點的同學有:
@monkingxue @alcat2008 @淡蒼 @camsong,精讀由此歸納。

HOC 的適用范圍

對比 HOC 范式 compose(render)(state) 與父組件(Parent Component)的范式 render(render(state)),如果完全利用 HOC 來實現 React 的 implement,將操作與 view 分離,也未嘗不可,但卻不優雅。HOC 本質上是統一功能抽象,強調邏輯與 UI 分離。但在實際開發中,前端無法逃離 DOM ,而邏輯與 DOM 的相關性主要呈現 3 種關聯形式:

  • 與 DOM 相關,建議使用父組件,類似于原生 HTML 編寫
  • 與 DOM 不相關,如校驗、權限、請求發送、數據轉換這類,通過數據變化間接控制 DOM,可以使用 HOC 抽象
  • 交叉的部分,DOM 相關,但可以做到完全內聚,即這些 DOM 不會和外部有關聯,均可

DOM 的渲染適合使用父組件,這是 React JSX 原生支持的方式,清晰易懂。最好是能封裝成木偶組件(Dumb Component)。HOC 適合做 DOM 不相關又是多個組件共性的操作。如 Form 中,validator 校驗操作就是純數據操作的,放到了 HOC 中。但 validator 信息沒有放到 HOC 中。但如果能把 Error 信息展示這些邏輯能夠完全隔離,也可以放到 HOC 中(可結合下一小節 Form 具體實踐詳細了解)。
數據請求是另一類 DOM 不相關的場景,react-refetch 的實現就是使用了 HOC,做到了高效和優雅:

connect(props => ({usersFetch: `/users?status=${props.status}&page=${props.page}`,userStatsFetch: { url: `/users/stats`, force: true }
}))(UsersList)

HOC 的具體實踐

HOC 在真實場景下的運行非常多,之前筆者在 基于 Decorator 的組件擴展實踐 一文中也提過使用高階組件將更細粒度的組件組合成 Selector 與 Search。結合精讀文章,這次讓我們通過 Form 組件的抽象來表現 HOC 具有的良好擴展機制。

Form 中會包含各種不同的組件,常見的有 Input、Selector、Checkbox 等等,也會有根據業務需求加入的自定義組件。Form 靈活多變,從功能上看,表單校驗可能為單組件值校驗,也可能為全表單值校驗,可能為常規檢驗,比如:非空、輸入限制,也可能需要與服務端配合,甚至需要根據業務特點進行定制。從 UI 上看,檢驗結果顯示的位置,可能在組件下方,也可能是在組件右側。

直接裸寫 Form,無疑是機械而又重復的。將 Form 中組件的 value 經過 validator,把 value,validator 產生的 error 信息儲存到 state 或 redux store 中,然后在 view 層完成顯示。這條路大家都是相同的,可以進行復用,只是我們面對的是不同的組件,不同的 validator,不同的 view 而已。對于 Form 而言,既要滿足通用,又要滿足部分個性化的需求,以往單純的配置化只會讓使用愈加繁瑣,我們所需要抽象的是 Form 功能而非 UI,因此通過 HOC 針對 Form 的功能進行提取就成為了必然。

image

至于 HOC 在 Form 上的具體實現,首先將表單中的組件(Input、Selector…)與相應 validator 與組件值回調函數名(trigger)傳入 Decorator,將 validator 與 trigger 相綁定。Decorator 完成了各種不同組件與 Form 內置 Store 間 value 的傳遞、校驗功能的抽象,即精讀文章中提到 Props Proxy 方式的其中兩種作用:提取 state操作 props

function formFactoryFactory({validator,trigger = 'onChange',...
}) {return FormFactory(WrappedComponent) {return class Decorator extends React.Component {getBind(trigger, validator) {...}render() {const newProps = {...this.props,[trigger]: this.getBind(trigger, validator),...}return <WrappedComponent {...newProps} />}}}
}// 調用
formFactoryFactory({validator: (value) => {return value !== '';}
})(<Input placeholder="請輸入..." />)

當然為了考慮個性化需求,Form Store 也向外暴露很多 API,可以直接獲取和修改 value、error 的值。現在我們需要對一個表單的所有值提交到后端進行校驗,根據后端返回,分別列出各項的校驗錯誤信息,就需要借助相應項的 setError 去完成了。

這里主要參考了 rc-form 的實現方式,有興趣的讀者可以閱讀其源碼。

import { createForm } from 'rc-form';class Form extends React.Component {submit = () => {this.props.form.validateFields((error, value) => {console.log(error, value);});}render() {const { getFieldError, getFieldDecorator } = this.props.form;const errors = getFieldError('required');return (<div>{getFieldDecorator('required', {rules: [{ required: true }],})(<Input />)}{errors ? errors.join(',') : null}<button onClick={this.submit}>submit</button></div>);}
}export createForm()(Form);

4 總結

React 始終強調組合優于繼承的理念,期望通過復用小組件來構建大組件使得開發變得簡單而又高效,與傳統面向對象思想是截然不同的。高階函數(HOC)的出現替代了原有 Mixin 侵入式的方案,對比隱式的 Mixin 或是繼承,HOC 能夠在 Devtools 中顯示出來,滿足抽象之余,也方便了開發與測試。當然,不可過度抽象是我們始終要秉持的原則。希望讀者通過本次閱讀與討論,能結合自己具體的業務開發場景,獲得一些啟發。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/711620.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/711620.shtml
英文地址,請注明出處:http://en.pswp.cn/news/711620.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【QT+QGIS跨平臺編譯】之五十三:【QGIS_CORE跨平臺編譯】—【qgssqlstatementparser.cpp生成】

文章目錄 一、Bison二、生成來源三、構建過程一、Bison GNU Bison 是一個通用的解析器生成器,它可以將注釋的無上下文語法轉換為使用 LALR (1) 解析表的確定性 LR 或廣義 LR (GLR) 解析器。Bison 還可以生成 IELR (1) 或規范 LR (1) 解析表。一旦您熟練使用 Bison,您可以使用…

transformers文本相似度

在自然語言處理(NLP)中,文本相似度是衡量兩個文本之間語義或結構相似程度的一個重要概念。計算文本相似度的方法多種多樣,適應不同的應用場景和需求。以下是一些常見的文本相似度計算方法: 1、余弦相似度: 通過將文本轉換為向量表示(例如,使用詞袋模型、TF-IDF 或 wor…

2024年個人護理賽道選品風向在哪?這份賽盈分銷選品攻略必看!

2024年還會卷下去嗎&#xff1f;看到一位行業大佬分享的內容深有感觸&#xff1a;堅定做好產品&#xff0c;不做大賣&#xff0c;就不存在卷不卷。 有人出局&#xff0c;也會有人入局&#xff0c;并且深耕領域做大做強。 專注口腔護理的Bitvae入行不到兩年&#xff0c;憑借一款…

C#學習(十四)——垃圾回收、析構與IDisposable

一、何為GC 數據是存儲在內存中的&#xff0c;而內存又分為Stack棧內存和Heap堆內存 Stack棧內存Heap堆內存速度快、效率高結構復雜類型、大小有限制對象只能保存簡單的數據引用數據類型基礎數據類型、值類型- 舉個例子 var c new Customer{id: 123,name: "Jack"…

Java中String類有哪些常用方法?

Java中的String類提供了許多有用的方法&#xff0c;用于處理字符串。以下是一些常用的方法及其簡要描述&#xff1a; 1. **charAt(int index)**&#xff1a;返回指定位置的字符。 2. **length()**&#xff1a;返回字符串的長度。 3. **substring(int beginIndex, int endInd…

微信小程序手勢沖突?不存在的!

原生的應用經常會有頁面嵌套列表&#xff0c;滾動列表能夠改變列表大小&#xff0c;然后還能支持列表內下拉刷新等功能。看了很多的小程序好像都沒有這個功能&#xff0c;難道這個算是原生獨享的嗎&#xff0c;難道是由于手勢沖突無法實現嗎&#xff0c;冷靜的思考了一下&#…

Google驗證碼,掃描綁定,SpringBoot+ vue

文章目錄 后端1.使用Google工具類這個 類的 verifyTest 方法可以判斷掃描綁定之后的app上面驗證碼的準確性。這個類通過g_user,g_code(就是谷歌驗證器的secret,這個你已經插入到數據庫 中)來生成相關二維碼。2.用工具類自帶的g_user,g_code來生成二維碼2.1通過請求來生成相關二…

你知道vector底層是如何實現的嗎?

你知道vector底層是如何實現的嗎&#xff1f; vector底層使用動態數組來存儲元素對象&#xff0c;同時使用size和capacity記錄當前元素的數量和當前動態數組的容量。如果持續的push_back(emplace_back)元素&#xff0c;當size大于capacity時&#xff0c;需要開辟一塊更大的動態…

【InternLM 實戰營筆記】XTuner 大模型單卡低成本微調實戰

XTuner概述 一個大語言模型微調工具箱。由 MMRazor 和 MMDeploy 聯合開發。 支持的開源LLM (2023.11.01) InternLM Llama&#xff0c;Llama2 ChatGLM2&#xff0c;ChatGLM3 Qwen Baichuan&#xff0c;Baichuan2 Zephyr 特色 傻瓜化&#xff1a; 以 配置文件 的形式封裝了大…

WebGIS----wenpack

學習資料&#xff1a;https://webpack.js.org/concepts/ 簡介&#xff1a; Webpack 是一個現代化的 JavaScript 應用程序的模塊打包工具。它能夠將多個 JavaScript 文件和它們的依賴打包成一個單獨的文件&#xff0c;以供在網頁中使用。 Webpack 還具有編譯和轉換其他類型文…

自學新標日第六課(單詞部分 未完結)

第六課 單詞 單詞假名聲調詞義來月らいげつ1下個月先月せんげつ1上個月夜中よなか3午夜昨夜ゆうべ0昨天晚上コンサートこんさーと1音樂會クリスマスくりすます3圣誕季誕生日たんじょうび&#xff13;生日こどもの日こどものひ&#xff15;兒童節夏休みなつやすみ&#xff13;…

看待事物的層與次 | DBA與架構的一次對話交流

前言 在計算機軟件業生涯中,想必行內人或多或少都能感受到系統架構設計與數據庫系統工程的重要性,也能夠清晰地認識到在計算機軟件行業中技術工程師這個職業所需要的專業素養和必備技能! 背景 通過自研的數據庫監控管理工具,發現 SQL Server 數據庫連接數在1-2K之間,想…

Yii2中如何使用scenario場景,使rules按不同運用進行字段驗證

Yii2中如何使用scenario場景&#xff0c;使rules按不同運用進行字段驗證 當創建news新聞form表單時&#xff1a; 添加新聞的時候執行create動作。 必填字段&#xff1a;title-標題&#xff0c;picture-圖片&#xff0c;description-描述。 這時候在model里News.php下rules規則…

星座每日運勢 api接口

接口數據api 接口平臺&#xff1a;https://api.yuanfenju.com/ 開發文檔&#xff1a;https://doc.yuanfenju.com/zhanbu/yunshi.html 支持格式&#xff1a;JSON 請求方式&#xff1a;HTTP POST <?php//您的密鑰 $api_secret "wD******XhOUW******pvr"; //請…

利用coze 搭建“全功能“微信客服(2)

緊跟上篇 利用coze 搭建"全功能"微信客服&#xff08;1&#xff09;&#xff0c;不知道來龍去脈自行查閱 先表揚下coze: coze 是國內少數開放平臺之一&#xff0c;里面提供各種插件還可以開發工作流&#xff0c;讓你可以實現多模態全功能大模型 吐槽 沒有API開放接口…

國外最流行的是AI,國內最流行的是AI培訓教程

國外最流行的是AI&#xff0c;國內最流行的是AI培訓教程。 最近李一舟AI教程事件&#xff0c;驗證了這句話。 如今給客戶做方案項目里能加點AI色彩&#xff0c;立項的成功率都變大(特別是事業單位)。 正因如此&#xff0c;大家都在狂補AI的知識&#xff0c;不然肚子里沒點墨水&…

2024亞馬遜全球開店注冊前需要準備什么?

在2023年出海四小龍SHEIN、Temu、速賣通AliExpress、TikTok Shop快速增長擴張&#xff0c;成為了中國跨境賣家“逃離亞馬遜”的新選擇。但是&#xff0c;跨境電商看亞馬遜。當前&#xff0c;亞馬遜仍然是跨境電商行業的絕對老大&#xff0c;占有將近70%成以上的業務份額。 作為…

threejs顯示本地硬盤上的ply文件,通過webapi

由于ply文件是第三方提供的&#xff0c;threejs無法用絕路路徑的方式顯示ply 所以想通過webapi把ply通過url地址的方式給threejs 1.webapi部分 /// <summary>/// 獲取PLY文件/// </summary>/// <returns></returns>[HttpPost(Name "GetPly&qu…

分享fastapi低級錯誤

我是創建表的時候把__tablename__ 寫成__table__然后一直報這個錯誤

Android Activity跳轉詳解

在Android應用程序中&#xff0c;Activity之間的跳轉是非常常見的操作&#xff0c;通過跳轉可以實現不同界面之間的切換和交互。在本篇博客中&#xff0c;我們將介紹Android中Activity跳轉的相關知識&#xff0c;包括基本跳轉、傳遞參數、返回數據以及跳轉到瀏覽器、撥號應用和…