摘要:為了代碼進一步解耦,可以考慮使用高階組件這種模式。開源的高階組件使用提供了一系列使用的高階組件,可以增強組件的行為,可以利用此庫學習高階組件的寫法。通過使用此庫提供的高階組件,可以方便地讓列表元素可拖動。
1. Decorator基本知識
在很多框架和庫中看到它的身影,尤其是React和Redux,還有mobx中,那什么是裝飾器呢。
修飾器(Decorator)是一個函數(shù),用來修改類的行為。不是很理解這種抽象概念,還是看代碼講解實際些。
//定義一個函數(shù),也就是定義一個Decorator,target參數(shù)就是傳進來的Class。 //這里是為類添加了一個靜態(tài)屬性 function addAge(target) { target.age = 2; } //在Decorator后面跟著Class,Decorator是函數(shù)的話,怎么不是addAge(MyGeekjcProject)這樣寫呢? //我只能這樣理解:因為語法就這樣,只要Decorator后面是Class,默認就已經(jīng)把Class當成參數(shù)隱形傳進Decorator了(就是所謂的語法糖)。 @addAge class MyGeekjcProject {} console.log(MyGeekjcProject.age) // 21.1 傳參修飾類
但上面這樣只傳了一個Class作為參數(shù)不夠靈活怎么辦?
我們可以在外層套一個函數(shù),只要最后返回的是一個Decorator即可,管你套多少個函數(shù)傳多少個參數(shù)都無所謂。
function addAge(age) { return function(target) { target.age = age; } } //注意這里,隱形傳入了Class,語法類似于addAge(2)(MyGeekjcProject) @testable(2) class MyGeekjcProject {} MyGeekjcProject.age // 2 @addAge(3) class MyGeekjcProject {} MyGeekjcProject.age // 31.2 修飾類的屬性
下面是修改類的prototype對象
function description(target) { target.prototype.url = "https://www.geekjc.com"; } @description class MyGeekjcProject {} let geekjc = new MyGeekjcProject(); geekjc.url // https://www.geekjc.com
概念大概理解了,修飾器不僅可以修飾類,還可以修飾類的屬性
//假如修飾類的屬性則傳入三個參數(shù),對應Object.defineProperty()里三個參數(shù),具體不細說 //target為目標對象,對應為Class的實例 //name為所要修飾的屬性名,這里就是修飾器緊跟其后的name屬性 //descriptor為該屬性的描述對象 //這里的Decorator作用是使name屬性不可寫,并返回修改后的descriptor function readonly(target, name, descriptor){ descriptor.writable = false; return descriptor; } class Person { @readonly name() { return `${this.first} ${this.last}` } }
關(guān)于Object.defineProperty()可以看文章學習Object.defineProperty
再看一個復雜的例子
//定義一個Class并在其add上使用了修飾器 class Math { @log add(a, b) { return a + b; } } //定義一個修飾器 function log(target, name, descriptor) { //這里是緩存舊的方法,也就是上面那個add()原始方法 var oldValue = descriptor.value; //這里修改了方法,使其作用變成一個打印函數(shù) //最后依舊返回舊的方法,真是巧妙 descriptor.value = function() { console.log(`Calling "${name}" with`, arguments); return oldValue.apply(null, arguments); }; return descriptor; } const math = new Math(); math.add(2, 4);
看完上面的代碼就知道decorator有什么用了,修飾器不僅可以修飾類的行為,還可以修飾類的屬性。
2. react高階組件更通俗地描述為,高階組件通過包裹(wrapped)被傳入的React組件,經(jīng)過一系列處理,最終返回一個相對增強(enhanced)的React組件,供其他組件調(diào)用。
3. 實現(xiàn)一個高階組件面我們來實現(xiàn)一個最簡單的高階組件(函數(shù)),它接受一個React組件,包裹后然后返回。
export default function withDescription(WrappedComponent) { return class HOC extends Component { render() { return} } }極客教程(https://www.geekjc.com)致力于推廣各種編程語言技術(shù),也為了未來數(shù)字化世界,讓人更容易找到操作數(shù)字化的方式,為了未來而生的編程學習平臺, 大部分資源是完全免費的,并且會根據(jù)當前互聯(lián)網(wǎng)的變化實時更新本站內(nèi)容。
在其他組件里,我們引用這個高階組件,用來強化它。
@withDescription export default class Geekjc extends Component { render() { return (我是一個普通組件); } }
在這里使用了ES7里的decorator(可以看第一節(jié)的Decorator),來提升寫法上的優(yōu)雅,但是實際上它只是一個語法糖,下面這種寫法也是可以的。
const?EnhanceDemo = withDescription(Geekjc);
隨后,觀察React組件樹發(fā)生了什么變化,如圖所示,可以發(fā)現(xiàn)Geekjc組件被HOC組件包裹起來了,符合了高階組件的預期,即組件是層層包裹起來的,如同洋蔥一樣。
但是隨之帶來的問題是,如果這個高階組件被使用了多次,那么在調(diào)試的時候,將會看到一大堆HOC,所以這個時候需要做一點小優(yōu)化,就是在高階組件包裹后,應當保留其原有名稱。
我們改寫一下上述的高階組件代碼,增加了getDisplayName函數(shù)以及靜態(tài)屬性displayName,此時再去觀察DOM Tree。
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} } }極客教程(https://www.geekjc.com)致力于推廣各種編程語言技術(shù),也為了未來數(shù)字化世界,讓人更容易找到操作數(shù)字化的方式,為了未來而生的編程學習平臺, 大部分資源是完全免費的,并且會根據(jù)當前互聯(lián)網(wǎng)的變化實時更新本站內(nèi)容。
此時,原本組件的名稱正確地顯示在了DOM Tree上。
這個簡單的例子里高階組件只做了一件事,那便是為被包裹的組件添加一個標題樣式。這個高階組件可以用到任何一個需要添加此邏輯的組件上,只需要被此高階組件修飾即可。
由此可以看出,高階組件的主要功能是封裝并抽離組件的通用邏輯,讓此部分邏輯在組件間更好地被復用。
4. 高階組件的進階用法 4.1 組件參數(shù)還是以上述例子為例,此高階組件僅僅只是展示了關(guān)于網(wǎng)站的描述,但是為了更好的抽象,此描述應當可以被參數(shù)化,如下方式調(diào)用。
@withDescription("歡迎大家訪問收藏(https://www.geekjc.com)") export default class Geekjc extends Component { render() { return ( //... ); } }
withDescription需要被改寫成如下形式,它接受一個參數(shù),然后返回一個高階組件(函數(shù))。
export default function (description) { return function (WrappedComponent) { return class HOC extends Component { render() { return} } } }{description ?description : "我是描述"}
使用ES6寫法可以更加簡潔。
export default(description) => (WrappedComponent) => class HOC extends Component { render() { return} }{description ? description : "我是描述"}
如圖可以看到,傳入的參數(shù)已經(jīng)反映在DOM Tree里了。
柯里化 Curry4.2 基于屬性代理的方式
概念:只傳遞函數(shù)的一部分參數(shù)來調(diào)用它,讓它返回一個函數(shù)去處理剩下的參數(shù)。
函數(shù)簽名:fun(params)(otherParams)
應用:在React里,通過柯里化,我們可以通過傳入不同的參數(shù)來得到不同的高階組件。
屬性代理是最常見的高階組件的使用方式,上述描述的高階組件就是這種方式。它通過做一些操作,將被包裹組件的props和新生成的props一起傳遞給此組件,這稱之為屬性代理。
export default function withHeader(WrappedComponent) { return class HOC extends Component { render() { const newProps = { test:"hoc" } // 透傳props,并且傳遞新的newProps return4.3 基于反向繼承的方式} } }
這種方式返回的React組件繼承了被傳入的組件,所以它能夠訪問到的區(qū)域、權(quán)限更多,相比屬性代理方式,它更像打入組織內(nèi)部,對其進行修改。具體的可以參考附錄里提供的鏈接進行深入學習。
export default function (WrappedComponent) { return class Inheritance extends WrappedComponent { componentDidMount() { // 可以方便地得到state,做一些更深入的修改。 console.log(this.state); } render() { return super.render(); } } }4.4 組合多個高階組件
上述高階組件為React組件增強了一個功能,如果需要同時增加多個功能需要怎么做?這種場景非常常見,例如我既需要增加一個組件標題,又需要在此組件未加載完成時顯示Loading。
@withDescription @withLoading class Demo extends Component{ }
使用compose可以簡化上述過程,也能體現(xiàn)函數(shù)式編程的思想。
const enhance = compose(withHeader,withLoading); @enhance class Demo extends Component{ }
組合 Compose5. 與父組件區(qū)別
compose可以幫助我們組合任意個(包括0個)高階函數(shù),例如compose(a,b,c)返回一個新的函數(shù)d,函數(shù)d依然接受一個函數(shù)作為入?yún)ⅲ徊贿^在內(nèi)部會依次調(diào)用c,b,a,從表現(xiàn)層對使用者保持透明。
基于這個特性,我們便可以非常便捷地為某個組件增強或減弱其特征,只需要去變更compose函數(shù)里的參數(shù)個數(shù)便可。
compose函數(shù)實現(xiàn)方式有很多種,這里推薦其中一個recompact.compose,詳情見下方參考類庫,也可以看我之前寫的一篇文章reduce與redux中compose函數(shù)
高階組件作為一個函數(shù),它可以更加純粹地關(guān)注業(yè)務邏輯層面的代碼,比如數(shù)據(jù)處理,數(shù)據(jù)校驗,發(fā)送請求等,可以改善目前代碼里業(yè)務邏輯和UI邏輯混雜在一起的現(xiàn)狀。父組件則是UI層的東西,我們先前經(jīng)常把一些業(yè)務邏輯處理放在父組件里,這樣會造成父組件混亂的情況。為了代碼進一步解耦,可以考慮使用高階組件這種模式。
6. 開源的高階組件使用 6.1 recompactrecompact提供了一系列使用的高階組件,可以增強組件的行為,可以利用此庫學習高階組件的寫法。
import recompact from "recompact" import { pure, withProps } from "recompact" const enhance = recompact.compose( withProps({ className: "beautiful" }), pure, ) @enhance class Demo extends Component{ }6.2 React Sortable
通過使用此庫提供的高階組件,可以方便地讓列表元素可拖動。
7. 參考recompact:包含了一系列實用的高階組件庫
React Sortable:React拖動庫
深入理解 React 高階組件:其中詳細介紹了屬性代理和反向繼承的區(qū)別。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/98699.html
摘要:博客地址背景知識在開始講述高階組件前,我們先來回顧高階函數(shù)的定義接收函數(shù)作為輸入,或者輸出另一個函數(shù)的一類函數(shù),被稱作高階函數(shù)。 博客地址:http://www.luckyjing.com/post... 背景知識 在開始講述高階組件前,我們先來回顧高階函數(shù)的定義:接收函數(shù)作為輸入,或者輸出另一個函數(shù)的一類函數(shù),被稱作高階函數(shù)。對于高階組件,它描述的便是接受React組件作為輸入,輸出...
摘要:但非常不幸,并不原生支持。這個單詞相信都很熟悉,高階函數(shù)在函數(shù)式編程是一個基本概念,它描述的是這樣一種函數(shù),接受函數(shù)作為輸入,或是輸出一個函數(shù)。比如常用的工具方法都是高階函數(shù)。恰與的定義完全一致。這種不同很可能會導致問題的產(chǎn)生。 在 React component 構(gòu)建過程中,常常有這樣的場景,有一類功能要被不同的 Component 公用,然后看得到文檔經(jīng)常提到 Mixin(混入) ...
閱讀 2288·2023-04-25 14:22
閱讀 3733·2021-11-15 18:12
閱讀 1293·2019-08-30 15:44
閱讀 3215·2019-08-29 15:37
閱讀 638·2019-08-29 13:49
閱讀 3454·2019-08-26 12:11
閱讀 866·2019-08-23 18:28
閱讀 1581·2019-08-23 14:55