高階元件的定義類比於高階函數的定義。高階函數接收函數作為參數,並且傳回值也是一個函數。類似的,高階元件接收React元件作為參數,並且傳回一個新的React元件。 高階元件本質上也是函數,並不是一個元件,這一點一定不要弄錯。
為什麼React引入高階元件的概念?它到底有何威力?讓我們先透過一個簡單的例子來說明一下。
假設有一個元件MyComponent
,需要從LocalStorage
取得數據,然後渲染資料到介面。我們可以這樣寫元件程式碼:
import React, { Component } from 'react' class MyComponent extends Component { componentWillMount() { let data = localStorage.getItem('data'); this.setState({data}); } render() { return <p>{this.state.data}</p> } }
程式碼很簡單,但當有其他元件也需要從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} 把传递给当前组件的属性继续传递给被包装的组件WrappedComponent return <wrappedcomponent></wrappedcomponent> } } } class MyComponent2 extends Component { render() { return <p>{this.props.data}</p> } } const MyComponentWithPersistentData = withPersistentData(MyComponent2)
withPersistentData
就是一個高階元件,它傳回一個新的元件,在新元件的componentWillMount
中統一處理從LocalStorage
中取得資料的邏輯,然後將取得的資料以屬性的方式傳遞給被包裝的元件WrappedComponent
,這樣在WrappedComponent
中就可以直接使用this.props. data
取得需要展示的資料了,如MyComponent2
所示。當有其他的元件也需要這段邏輯時,繼續使用withPersistentData
這個高階元件包裝這些元件就可以了。
透過這個例子,可以看出高階元件的主要功能是封裝並分離元件的通用邏輯,讓通用邏輯在元件間更好地被重複使用。高階組件的這種實現方式,本質上是一個裝飾者設計模式。
高階元件的參數並非只能是一個元件,它還可以接收其他參數。例如,元件MyComponent3
需要從LocalStorage中取得key等於name的數據,而不是上面範例中寫死的key等於data的數據,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} 把传递给当前组件的属性继续传递给被包装的组件WrappedComponent return <wrappedcomponent></wrappedcomponent> } } } class MyComponent2 extends Component { render() { return <p>{this.props.data}</p> } //省略其他逻辑... } class MyComponent3 extends Component { render() { return <p>{this.props.data}</p> } //省略其他逻辑... } const MyComponent2WithPersistentData = withPersistentData(MyComponent2, 'data'); const MyComponent3WithPersistentData = withPersistentData(MyComponent3, 'name');
新版本的withPersistentData
就滿足我們取得不同key的值的需求了。高階元件中的參數當然也可以是函數,我們將在下一節進一步說明。
3. 進階用法
高階元件最常見的函式簽章形式是這樣的:
HOC([param])([WrappedComponent])
用這種形式改寫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} 把传递给当前组件的属性继续传递给被包装的组件WrappedComponent return <wrappedcomponent></wrappedcomponent> } } } class MyComponent2 extends Component { render() { return <p>{this.props.data}</p> } //省略其他逻辑... } class MyComponent3 extends Component { render() { return <p>{this.props.data}</p> } //省略其他逻辑... } const MyComponent2WithPersistentData = withPersistentData('data')(MyComponent2); const MyComponent3WithPersistentData = withPersistentData('name')(MyComponent3);
實際上,此時的withPersistentData
和我們最初對高階組件的定義已經不同。它已經變成了一個高階函數,但這個高階函數的回傳值是一個高階元件。 HOC([param])([WrappedComponent])
這種形式中,HOC([param])
才是真正的高階元件,我們可以把它看成高階組件的變種形式。這種形式的高階組件因其特有的便利性——結構清晰(普通參數和被包裹組件分離)、易於組合,並大量出現在第三方庫中。如react-redux中的connect就是一個典型。 connect的定義如下:
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])(WrappedComponent)
這個函數會將一個React元件連接到Redux 的 store。在連接的過程中,connect透過函數類型的參數mapStateToProps
,從全域store中取出目前元件所需的state,並將state轉換成目前元件的props;同時透過函數類型的參數 mapDispatchToProps
,把目前元件用到的Redux的action creators,以props的方式傳遞給目前元件。
例如,我們把元件ComponentA連接到Redux上的寫法類似於:
const ConnectedComponentA = connect(mapStateToProps, mapDispatchToProps)(ComponentA);
我們可以把它拆分來看:
// connect 是一个函数,返回值enhance也是一个函数 const enhance = connect(mapStateToProps, mapDispatchToProps); // enhance是一个高阶组件 const ConnectedComponentA = enhance(ComponentA);
当多个函数的输出和它的输入类型相同时,这些函数是很容易组合到一起使用的。例如,有f,g,h三个高阶组件,都只接受一个组件作为参数,于是我们可以很方便的嵌套使用它们:f( g( h(WrappedComponent) ) )
。这里可以有一个例外,即最内层的高阶组件h可以有多个参数,但其他高阶组件必须只能接收一个参数,只有这样才能保证内层的函数返回值和外层的函数参数数量一致(都只有1个)。
例如我们将connect和另一个打印日志的高阶组件withLog
联合使用:
const ConnectedComponentA = connect(mapStateToProps)(withLog(ComponentA));
这里我们定义一个工具函数:compose(...functions)
,调用compose(f, g, h)
等价于 (...args) => f(g(h(...args)))
。用compose
函数我们可以把高阶组件嵌套的写法打平:
const enhance = compose( connect(mapStateToProps), withLog ); const ConnectedComponentA = enhance(ComponentA);
像Redux等很多第三方库都提供了compose
的实现,compose
结合高阶组件使用,可以显著提高代码的可读性和逻辑的清晰度。
4.与父组件区别
有些同学可能会觉得高阶组件有些类似父组件的使用。例如,我们完全可以把高阶组件中的逻辑放到一个父组件中去执行,执行完成的结果再传递给子组件。从逻辑的执行流程上来看,高阶组件确实和父组件比较相像,但是高阶组件强调的是逻辑的抽象。高阶组件是一个函数,函数关注的是逻辑;父组件是一个组件,组件主要关注的是UI/DOM。如果逻辑是与DOM直接相关的,那么这部分逻辑适合放到父组件中实现;如果逻辑是与DOM不直接相关的,那么这部分逻辑适合使用高阶组件抽象,如数据校验、请求发送等。
5. 注意事项
1)不要在组件的render方法中使用高阶组件,尽量也不要在组件的其他生命周期方法中使用高阶组件。因为高阶组件每次都会返回一个新的组件,在render中使用会导致每次渲染出来的组件都不相等(===
),于是每次render,组件都会卸载(unmount),然后重新挂载(mount),既影响了效率,又丢失了组件及其子组件的状态。高阶组件最适合使用的地方是在组件定义的外部,这样就不会受到组件生命周期的影响了。
2)如果需要使用被包装组件的静态方法,那么必须手动拷贝这些静态方法。因为高阶组件返回的新组件,是不包含被包装组件的静态方法。hoist-non-react-statics可以帮助我们方便的拷贝组件所有的自定义静态方法。有兴趣的同学可以自行了解。
3)Refs不会被传递给被包装组件。尽管在定义高阶组件时,我们会把所有的属性都传递给被包装组件,但是ref
并不会传递给被包装组件。如果你在高阶组件的返回组件中定义了ref
,那么它指向的是这个返回的新组件,而不是内部被包装的组件。如果你希望获取被包装组件的引用,你可以把ref
的回调函数定义成一个普通属性(给它一个ref以外的名字)。下面的例子就用inputRef这个属性名代替了常规的ref命名:
function FocusInput({ inputRef, ...rest }) { return <input>; } //enhance 是一个高阶组件 const EnhanceInput = enhance(FocusInput); // 在一个组件的render方法中... return (<enhanceinput> { this.input = input } }>) // 让FocusInput自动获取焦点 this.input.focus();</enhanceinput>
下篇预告:
React 深入系列7:React 常用模式
我的新书《React进阶之路》已上市,请大家多多支持!
链接:京东 当当
React 深入系列,深入讲解了React中的重点概念、特性和模式等,旨在帮助大家加深对React的理解,以及在项目中更加灵活地使用React。
1. 基本概念
高阶组件是React 中一个很重要且比较复杂的概念,高阶组件在很多第三方库(如Redux)中都被经常使用。在项目中用好高阶组件,可以显著提高代码质量。
高階組件的定義類比於高階函數的定義。高階函數接收函數作為參數,並且傳回值也是一個函數。類似的,高階元件接收React元件作為參數,並且傳回一個新的React元件。 高階元件本質上也是一個函數,並不是一個元件,這一點一定不要弄錯。
2. 應用場景
為什麼React引入高階元件的概念?它到底有何威力?讓我們先透過一個簡單的例子來說明一下。
假設有一個元件MyComponent
,需要從LocalStorage
取得數據,然後渲染資料到介面。我們可以這樣寫元件程式碼:
import React, { Component } from 'react' class MyComponent extends Component { componentWillMount() { let data = localStorage.getItem('data'); this.setState({data}); } render() { return <p>{this.state.data}</p> } }
程式碼很簡單,但當有其他元件也需要從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} 把传递给当前组件的属性继续传递给被包装的组件WrappedComponent return <wrappedcomponent></wrappedcomponent> } } } class MyComponent2 extends Component { render() { return <p>{this.props.data}</p> } } const MyComponentWithPersistentData = withPersistentData(MyComponent2)
withPersistentData
就是一個高階元件,它傳回一個新的元件,在新元件的componentWillMount
中統一處理從LocalStorage
中取得資料的邏輯,然後將取得的資料以屬性的方式傳遞給被包裝的元件WrappedComponent
,這樣在WrappedComponent
中就可以直接使用this.props. data
取得需要展示的資料了,如MyComponent2
所示。當有其他的元件也需要這段邏輯時,繼續使用withPersistentData
這個高階元件包裝這些元件就可以了。
透過這個例子,可以看出高階元件的主要功能是封裝並分離元件的通用邏輯,讓通用邏輯在元件間更好地被重複使用。高階組件的這種實現方式,本質上是一個裝飾者設計模式。
高階元件的參數並非只能是一個元件,它還可以接收其他參數。例如,元件MyComponent3
需要從LocalStorage中取得key等於name的數據,而不是上面範例中寫死的key等於data的數據,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} 把传递给当前组件的属性继续传递给被包装的组件WrappedComponent return <wrappedcomponent></wrappedcomponent> } } } class MyComponent2 extends Component { render() { return <p>{this.props.data}</p> } //省略其他逻辑... } class MyComponent3 extends Component { render() { return <p>{this.props.data}</p> } //省略其他逻辑... } const MyComponent2WithPersistentData = withPersistentData(MyComponent2, 'data'); const MyComponent3WithPersistentData = withPersistentData(MyComponent3, 'name');
新版本的withPersistentData
就滿足我們取得不同key的值的需求了。高階元件中的參數當然也可以是函數,我們將在下一節進一步說明。
3. 進階用法
高階元件最常見的函式簽章形式是這樣的:
HOC([param])([WrappedComponent])
用這種形式改寫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} 把传递给当前组件的属性继续传递给被包装的组件WrappedComponent return <wrappedcomponent></wrappedcomponent> } } } class MyComponent2 extends Component { render() { return <p>{this.props.data}</p> } //省略其他逻辑... } class MyComponent3 extends Component { render() { return <p>{this.props.data}</p> } //省略其他逻辑... } const MyComponent2WithPersistentData = withPersistentData('data')(MyComponent2); const MyComponent3WithPersistentData = withPersistentData('name')(MyComponent3);
實際上,此時的withPersistentData
和我們最初對高階組件的定義已經不同。它已經變成了一個高階函數,但這個高階函數的回傳值是一個高階元件。 HOC([param])([WrappedComponent])
這種形式中,HOC([param])
才是真正的高階元件,我們可以把它看成高階組件的變種形式。這種形式的高階組件因其特有的便利性——結構清晰(普通參數和被包裹組件分離)、易於組合,並大量出現在第三方庫中。如react-redux中的connect就是一個典型。 connect的定義如下:
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])(WrappedComponent)
這個函數會將一個React元件連接到Redux 的 store。在連接的過程中,connect透過函數類型的參數mapStateToProps
,從全域store中取出目前元件所需的state,並將state轉換成目前元件的props;同時透過函數類型的參數 mapDispatchToProps
,把目前元件用到的Redux的action creators,以props的方式傳遞給目前元件。
例如,我們把元件ComponentA連接到Redux上的寫法類似於:
const ConnectedComponentA = connect(mapStateToProps, mapDispatchToProps)(ComponentA);
我們可以把它拆分來看:
// connect 是一个函数,返回值enhance也是一个函数 const enhance = connect(mapStateToProps, mapDispatchToProps); // enhance是一个高阶组件 const ConnectedComponentA = enhance(ComponentA);
當多個函數的輸出和它的輸入類型相同時,這些函數是很容易組合到一起使用的。例如,有f,g,h三個高階元件,都只接受一個元件作為參數,於是我們可以很方便的巢狀使用它們:f( g( h(WrappedComponent) ) )
。這裡可以有一個例外,即最內層的高階元件h可以有多個參數,但其他高階元件必須只能接收一個參數,只有這樣才能確保內層的函數回傳值和外層的函數參數數量一致(都只有1個)。
例如我們將connect和另一個列印日誌的高階元件withLog
結合使用:
const ConnectedComponentA = connect(mapStateToProps)(withLog(ComponentA));
这里我们定义一个工具函数:compose(...functions)
,调用compose(f, g, h)
等价于 (...args) => f(g(h(...args)))
。用compose
函数我们可以把高阶组件嵌套的写法打平:
const enhance = compose( connect(mapStateToProps), withLog ); const ConnectedComponentA = enhance(ComponentA);
像Redux等很多第三方库都提供了compose
的实现,compose
结合高阶组件使用,可以显著提高代码的可读性和逻辑的清晰度。
4.与父组件区别
有些同学可能会觉得高阶组件有些类似父组件的使用。例如,我们完全可以把高阶组件中的逻辑放到一个父组件中去执行,执行完成的结果再传递给子组件。从逻辑的执行流程上来看,高阶组件确实和父组件比较相像,但是高阶组件强调的是逻辑的抽象。高阶组件是一个函数,函数关注的是逻辑;父组件是一个组件,组件主要关注的是UI/DOM。如果逻辑是与DOM直接相关的,那么这部分逻辑适合放到父组件中实现;如果逻辑是与DOM不直接相关的,那么这部分逻辑适合使用高阶组件抽象,如数据校验、请求发送等。
5. 注意事项
1)不要在组件的render方法中使用高阶组件,尽量也不要在组件的其他生命周期方法中使用高阶组件。因为高阶组件每次都会返回一个新的组件,在render中使用会导致每次渲染出来的组件都不相等(===
),于是每次render,组件都会卸载(unmount),然后重新挂载(mount),既影响了效率,又丢失了组件及其子组件的状态。高阶组件最适合使用的地方是在组件定义的外部,这样就不会受到组件生命周期的影响了。
2)如果需要使用被包装组件的静态方法,那么必须手动拷贝这些静态方法。因为高阶组件返回的新组件,是不包含被包装组件的静态方法。hoist-non-react-statics可以帮助我们方便的拷贝组件所有的自定义静态方法。有兴趣的同学可以自行了解。
3)Refs不会被传递给被包装组件。尽管在定义高阶组件时,我们会把所有的属性都传递给被包装组件,但是ref
并不会传递给被包装组件。如果你在高阶组件的返回组件中定义了ref
,那么它指向的是这个返回的新组件,而不是内部被包装的组件。如果你希望获取被包装组件的引用,你可以把ref
的回调函数定义成一个普通属性(给它一个ref以外的名字)。下面的例子就用inputRef这个属性名代替了常规的ref命名:
function FocusInput({ inputRef, ...rest }) { return <input>; } //enhance 是一个高阶组件 const EnhanceInput = enhance(FocusInput); // 在一个组件的render方法中... return (<enhanceinput> { this.input = input } }>) // 让FocusInput自动获取焦点 this.input.focus();</enhanceinput>
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
react实现选中li高亮步骤详解
EasyCanvas绘图库在Pixeler项目开发中使用实战总结
以上是React高階組件使用詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

JavaScript是現代網站的核心,因為它增強了網頁的交互性和動態性。 1)它允許在不刷新頁面的情況下改變內容,2)通過DOMAPI操作網頁,3)支持複雜的交互效果如動畫和拖放,4)優化性能和最佳實踐提高用戶體驗。

C 和JavaScript通過WebAssembly實現互操作性。 1)C 代碼編譯成WebAssembly模塊,引入到JavaScript環境中,增強計算能力。 2)在遊戲開發中,C 處理物理引擎和圖形渲染,JavaScript負責遊戲邏輯和用戶界面。

JavaScript在網站、移動應用、桌面應用和服務器端編程中均有廣泛應用。 1)在網站開發中,JavaScript與HTML、CSS一起操作DOM,實現動態效果,並支持如jQuery、React等框架。 2)通過ReactNative和Ionic,JavaScript用於開發跨平台移動應用。 3)Electron框架使JavaScript能構建桌面應用。 4)Node.js讓JavaScript在服務器端運行,支持高並發請求。

Python更適合數據科學和自動化,JavaScript更適合前端和全棧開發。 1.Python在數據科學和機器學習中表現出色,使用NumPy、Pandas等庫進行數據處理和建模。 2.Python在自動化和腳本編寫方面簡潔高效。 3.JavaScript在前端開發中不可或缺,用於構建動態網頁和單頁面應用。 4.JavaScript通過Node.js在後端開發中發揮作用,支持全棧開發。

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。1)C 用于解析JavaScript源码并生成抽象语法树。2)C 负责生成和执行字节码。3)C 实现JIT编译器,在运行时优化和编译热点代码,显著提高JavaScript的执行效率。

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

MantisBT
Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

EditPlus 中文破解版
體積小,語法高亮,不支援程式碼提示功能

ZendStudio 13.5.1 Mac
強大的PHP整合開發環境

Safe Exam Browser
Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)