前言
HOC(高階組件)是React中的一種組織代碼的手段,而不是一個(gè)API.
這種設(shè)計(jì)模式可以復(fù)用在React組件中的代碼與邏輯,因?yàn)橐话銇?lái)講React組件比較容易復(fù)用渲染函數(shù), 也就是主要負(fù)責(zé)HTML的輸出.
高階組件實(shí)際上是經(jīng)過(guò)一個(gè)包裝函數(shù)返回的組件,這類(lèi)函數(shù)接收React組件處理傳入的組件,然后返回一個(gè)新的組件.
注意:前提是建立在不修改原有組件的基礎(chǔ)上.
文字描述太模糊,借助于官方文檔稍稍修改,我們可以更加輕松的理解高階組件.
具體的實(shí)施流程如下:
找出組件中復(fù)用的邏輯
創(chuàng)建適用于上方邏輯的函數(shù)
利用這個(gè)函數(shù)來(lái)創(chuàng)建一個(gè)組件
enjoy it
找出組件中復(fù)用的邏輯在實(shí)際開(kāi)發(fā)中, 這種邏輯的組件非常常見(jiàn):
組件創(chuàng)建
向服務(wù)器拉取數(shù)據(jù)
利用數(shù)據(jù)渲染組件
監(jiān)聽(tīng)數(shù)據(jù)的變化
數(shù)據(jù)變化或者觸發(fā)修改的事件
利用變化后的數(shù)據(jù)再次渲染
組件銷(xiāo)毀移除監(jiān)聽(tīng)的數(shù)據(jù)源
首先我們來(lái)創(chuàng)建一個(gè)生產(chǎn)假數(shù)據(jù)的對(duì)象來(lái)模擬數(shù)據(jù)源:
const fakeDataGenerator = ()=>({ timer: undefined, getData(){ return ["hello", "world"]; }, addChangeListener(handleChangeFun){ // 監(jiān)聽(tīng)數(shù)據(jù)產(chǎn)生鉤子 if(this.timer){ return; } this.timer = setInterval(()=> { handleChangeFun(); },2000) }, removeChangeListener(){ // 停止數(shù)據(jù)監(jiān)聽(tīng) clearInterval(this.timer); } });
然后來(lái)編寫(xiě)我們的組件A:
const FakeDataForA = fakeDataGenerator(); class A extends React.Component { constructor(props) {// 1 組件創(chuàng)建 super(props); this.state = { someData: fakeData.getData() // 1.1 向服務(wù)器拉取數(shù)據(jù) } } handleFakeDataChange = ()=>{ this.setState({ someData:fakeData.getData() // 4. 數(shù)據(jù)變化或者觸發(fā)修改的事件 }); } componentDidMount(){ // 3. 監(jiān)聽(tīng)數(shù)據(jù)的變化 // 4. 數(shù)據(jù)變化或者觸發(fā)修改的事件 fakeData.addChangeListener(this.handleFakeDataChange); } componentWillUnmount(){ fakeData.removeChangeListener(); // 6. 組件銷(xiāo)毀移除監(jiān)聽(tīng)的數(shù)據(jù)源 } render() { return ( {/* 2. 利用數(shù)據(jù)渲染組件 5. 利用變化后的數(shù)據(jù)再次渲染 */} this.state.someData.map(name => ({name})) ) } } ReactDOM.render(, document.getElementById("root"));
然后我們?cè)賮?lái)創(chuàng)建一個(gè)組件B這個(gè)雖然渲染方式不同,但是數(shù)據(jù)獲取的邏輯是一致的.
在一般的開(kāi)發(fā)過(guò)程中實(shí)際上也是遵循這個(gè)請(qǐng)求模式的,然后創(chuàng)建一個(gè)組件B:
const FakeDataForB = fakeDataGenerator(); class B extends React.Component { constructor(props) {// 1 組件創(chuàng)建 super(props); this.state = { someData: fakeData.getData() // 1.1 向服務(wù)器拉取數(shù)據(jù) } } handleFakeDataChange = ()=>{ this.setState({ someData:fakeData.getData() // 4. 數(shù)據(jù)變化或者觸發(fā)修改的事件 }); } componentDidMount(){ // 3. 監(jiān)聽(tīng)數(shù)據(jù)的變化 // 4. 數(shù)據(jù)變化或者觸發(fā)修改的事件 fakeData.addChangeListener(this.handleFakeDataChange); } componentWillUnmount(){ fakeData.removeChangeListener(); // 6. 組件銷(xiāo)毀移除監(jiān)聽(tīng)的數(shù)據(jù)源 } render() { return ( {/* 2. 利用數(shù)據(jù)渲染組件 5. 利用變化后的數(shù)據(jù)再次渲染 */} this.state.someData.map(name => ({name})) ) } } ReactDOM.render(, document.getElementById("root"));
這里我把redner中原來(lái)渲染的span標(biāo)簽改為了div標(biāo)簽,雖然這是一個(gè)小小的變化但是請(qǐng)你腦補(bǔ)這是兩個(gè)渲染結(jié)果完全不同的組件好了.
這時(shí)候問(wèn)題以及十分明顯了組件A和B明顯有大量的重復(fù)邏輯但是借助于React組件卻無(wú)法將這公用的邏輯來(lái)抽離.
在一般的開(kāi)發(fā)中沒(méi)有這么完美重復(fù)的邏輯代碼,例如在生命周期函數(shù)中B組件可能多了幾個(gè)操作或者A組件數(shù)據(jù)源獲取的地址不同.
但是這里依然存在大量的可以被復(fù)用的邏輯.
這種函數(shù)的第一個(gè)參數(shù)接收一個(gè)React組件,然后返回這個(gè)組件:
function MyHoc(Wrap) { return class extends React.Component{ render(){} } }
就目前來(lái)說(shuō)這個(gè)函數(shù)沒(méi)有任何實(shí)際功能只是將原有的組件包裝返回而已.
但是如果我們將組件A和B傳入到這個(gè)函數(shù)中,而使用返回的函數(shù),我們可以得到了什么.
我們獲取了在原有的組件上的一層包裝,利用這層包裝我們可以把組件A和B的共同邏輯提取到這層包裝上.
我們來(lái)刪除組件A和B有關(guān)數(shù)據(jù)獲取以及修改的操作:
class A extends React.Component { componentDidMount(){ // 這里執(zhí)行某些操作 假設(shè)和另外一個(gè)組件不同 } componentWillUnmount(){ // 這里執(zhí)行某些操作 假設(shè)和另外一個(gè)組件不同 } render() { return ( this.state.data.map(name => ({name})) ) } } class B extends React.Component { componentDidMount(){ // 這里執(zhí)行某些操作 假設(shè)和另外一個(gè)組件不同 } componentWillUnmount(){ // 這里執(zhí)行某些操作 假設(shè)和另外一個(gè)組件不同 } render() { return ( this.state.data.map(name => ({name})) ) } }
然后將在這層包裝上的獲取到的外部數(shù)據(jù)使用props來(lái)傳遞到原有的組件中:
function MyHoc(Wrap) { return class extends React.Component{ constructor(props){ super(props); this.state = { data:fakeData // 假設(shè)這樣就獲取到了數(shù)據(jù), 先不考慮其他情況 } } render(){ return{/* 通過(guò) props 把獲取到的數(shù)據(jù)傳入 */} } } }
在這里我們?cè)?HOC 返回的組件中獲取數(shù)據(jù), 然后把數(shù)據(jù)傳入到內(nèi)部的組件中, 那么數(shù)據(jù)獲取的這種功能就被多帶帶的拿了出來(lái).
這樣組件A和B只要關(guān)注自己的 props.data 就可以了完全不需要考慮數(shù)據(jù)獲取和自身的狀態(tài)修改.
但是我們注意到了組件A和B原有獲取數(shù)據(jù)源不同,我們?nèi)绾卧诎b函數(shù)中處理?
這點(diǎn)好解決,利用函數(shù)的參數(shù)差異來(lái)抹消掉返回的高階組件的差異.
既然A組件和B組件的數(shù)據(jù)源不同那么這個(gè)函數(shù)就另外接收一個(gè)數(shù)據(jù)源作為參數(shù)好了.
并且我們將之前通用的邏輯放到了這個(gè)內(nèi)部的組件上:
function MyHoc(Wrap,fakeData) { // 這次我們接收一個(gè)數(shù)據(jù)源 return class extends React.Component{ constructor(props){ super(props); this.state = { data: fakeData.getData() // 模擬數(shù)據(jù)獲取 } } handleDataChange = ()=>{ this.setState({ data:fakeData.getData() }); } componentDidMount() { fakeData.addChangeListener(this.handleDataChange); } componentWillUnmount(){ fakeData.removeChangeListener(); } render(){利用高階組件來(lái)創(chuàng)建組件} } }
經(jīng)過(guò)上面的思考,實(shí)際上已經(jīng)完成了99%的工作了,接下來(lái)就是完成剩下的1%,把它們組合起來(lái).
偽代碼:
const FakeDataForA = FakeDataForAGenerator(), FakeDataForB = FakeDataForAGenerator(); // 兩個(gè)不同的數(shù)據(jù)源 function(Wrap,fakdData){ // 一個(gè) HOC 函數(shù) return class extends React.Components{}; } class A {}; // 兩個(gè)不同的組件 class B {}; // 兩個(gè)不同的組件 const AFromHoc = MyHoc(A,FakeDataForA), BFromHoc = MyHoc(B,FakeDataForB); // 分別把不同的數(shù)據(jù)源傳入, 模擬者兩個(gè)組件需要不同的數(shù)據(jù)源, 但是獲取數(shù)據(jù)邏輯一致
這個(gè)時(shí)候你就可以渲染自己的高階組件AFromHoc和BFromHoc了.
這兩個(gè)組件使用不同的數(shù)據(jù)源來(lái)獲取數(shù)據(jù),通用的部分已經(jīng)被抽離.
HOC函數(shù)需要像透明的一樣,經(jīng)過(guò)他的包裝產(chǎn)生的新的組件和傳入前沒(méi)有什么區(qū)別.
這樣做的目的在于,我們不需要考慮經(jīng)過(guò)HOC函數(shù)后的組件會(huì)產(chǎn)生什么變化而帶來(lái)額外的心智負(fù)擔(dān).
如果你的HOC函數(shù)對(duì)傳入的組件進(jìn)行了修改,那么套用這種HOC函數(shù)多次后返回的組件在使用的時(shí)候.
你不得不考慮這個(gè)組件帶來(lái)的一些非預(yù)期行為.
所以請(qǐng)不要將原本組件不需要的props傳入:
render() { // 過(guò)濾掉非此 HOC 額外的 props,且不要進(jìn)行透?jìng)? const { extraProp, ...passThroughProps } = this.props; // 將 props 注入到被包裝的組件中。 // 通常為 state 的值或者實(shí)例方法。 const injectedProp = someStateOrInstanceMethod; // 將 props 傳遞給被包裝組件 return (HOC是函數(shù)!利用函數(shù)來(lái)最大化組合性); }
因?yàn)镠OC是一個(gè)返回組件的函數(shù),只要是函數(shù)可以做的事情HOC同樣可以做到.
利用這一點(diǎn),我們可以借用在使用React之前我們就已經(jīng)學(xué)會(huì)的一些東西.
例如定義一個(gè)高階函數(shù)用于返回一個(gè)高階組件:
function HighLevelHoc(content) { return function (Wrap, className) { return class extends React.Component { render() { return ({content} ) } } } } class Test extends React.Component { render() { return ({this.props.children || "hello world"}
) } } const H1Test = HighLevelHoc("foobar")(Test, 1); ReactDOM.render(, document.getElementById("root"));
或者干脆是一個(gè)不接收任何參數(shù)的函數(shù):
function DemoHoc(Wrap) { // 用于向 Wrap 傳入一個(gè)固定的字符串 return class extends React.Component{ render(){ return (注意 不要在 render 方法中使用 HOC{"hello world"} ) } } } function Demo(props) { return ({props.children}) } const App = DemoHoc(Demo); ReactDOM.render(, document.getElementById("root"));
我們都知道 React 會(huì)調(diào)用 render 方法來(lái)渲染組件, 當(dāng)然 React 也會(huì)做一些額外的工作例如性能優(yōu)化.
在組件重新渲染的時(shí)候 React 會(huì)判斷當(dāng)前 render 返回的組件和未之前的組件是否相等 === 如果相等 React 會(huì)遞歸更新組件, 反之他會(huì)徹底的卸載之前的舊的版本來(lái)渲染當(dāng)前的組件.
HOC每次返回的內(nèi)容都是一個(gè)新的內(nèi)容:
function Hoc(){ return {} } console.log( Hoc()===Hoc() ) // false
如果在 render 方法中使用:
render() { const DemoHoc = Hoc(MyComponent); // 每次調(diào)用 render 都會(huì)返回一個(gè)新的對(duì)象 // 這將導(dǎo)致子樹(shù)每次渲染都會(huì)進(jìn)行卸載,和重新掛載的操作! return記得復(fù)制靜態(tài)方法; }
React 的組件一般是繼承 React.Component 的子類(lèi).
不要忘記了一個(gè)類(lèi)上除了實(shí)例方法外還有靜態(tài)方法, 使用 HOC 我們對(duì)組件進(jìn)行了一層包裝會(huì)覆蓋掉原來(lái)的靜態(tài)方法:
class Demo extends React.Component{ render(){ return ({this.props.children}) } } Demo.echo = function () { console.log("hello world"); } Demo.echo();// 是可以調(diào)用的 // -------- 定一個(gè)類(lèi)提供一個(gè)靜態(tài)方法 function DemoHoc(Wrap) { return class extends React.Component{ render(){ return ({"hello world"} ) } } } const App = DemoHoc(Demo); // ----- HOC包裝這個(gè)類(lèi) App.echo(); // error 這個(gè)靜態(tài)方法不見(jiàn)了
解決方式
在 HOC 內(nèi)部直接將原來(lái)組件的靜態(tài)方法復(fù)制就可以了:
function DemoHoc(Wrap) { const myClass = class extends React.Component{ render(){ return ({"hello world"} ) } } myClass.echo = Wrap.echo; return myClass; }
不過(guò)這樣一來(lái) HOC 中就需要知道被復(fù)制的靜態(tài)方法名是什么, 結(jié)合之前提到的靈活使用 HOC 我們可以讓 HOC 接收靜態(tài)方法參數(shù)名稱(chēng):
function DemoHoc(Wrap,staticMethods=[]) { // 默認(rèn)空數(shù)組 const myClass = class extends React.Component{ render(){ return ({"hello world"} ) } } for (const methodName of staticMethods) { // 循環(huán)復(fù)制 myClass[methodName] = Wrap[methodName]; } return myClass; } // ----- const App = DemoHoc(Demo,["echo"]);
此外一般我們編寫(xiě)組件的時(shí)候都是一個(gè)文件對(duì)應(yīng)一個(gè)組件, 這時(shí)候我們可以把靜態(tài)方法導(dǎo)出.
HOC 不拷貝靜態(tài)方法, 而是需要這些靜態(tài)方法的組件直接引入就好了:
來(lái)自官方文檔
// 使用這種方式代替... MyComponent.someFunction = someFunction; export default MyComponent; // ...多帶帶導(dǎo)出該方法... export { someFunction }; // ...并在要使用的組件中,import 它們 import MyComponent, { someFunction } from "./MyComponent.js";透?jìng)?ref
ref 作為組件上的特殊屬性, 無(wú)法像普通的 props 那樣被向下傳遞.
例如我們有一個(gè)組件, 我們想使用 ref 來(lái)引用這個(gè)組件并且試圖調(diào)用它的 echo 方法:
class Wraped extends React.Component{ constructor(props){ super(props); this.state = { message:"" } } echo(){ this.setState({ message:"hello world" }); } render(){ return{this.state.message}} }
我們使用一個(gè) HOC 包裹它:
function ExampleHoc(Wrap) { return class extends React.Component{ render(){ return} } } const Example = ExampleHoc(Wraped); // 得到了一個(gè)高階組件
現(xiàn)在我們把這個(gè)組件放入到 APP 組件中進(jìn)行渲染, 并且使用 ref 來(lái)引用這個(gè)返回的組件, 并且試圖調(diào)用它的 echo 方法:
const ref = React.createRef(); class App extends React.Component { handleEcho = () => { ref.current.echo(); } render() { return () } }{/* 點(diǎn)擊按鈕相當(dāng)于執(zhí)行echo */}
但是當(dāng)你點(diǎn)擊按鈕試圖觸發(fā)子組件的事件的時(shí)候它不會(huì)起作用, 系統(tǒng)報(bào)錯(cuò)沒(méi)有 echo 方法.
實(shí)際上 ref 被綁定到了 HOC 返回的那個(gè)匿名類(lèi)上, 想要綁定到內(nèi)部的組件中我們可以進(jìn)行 ref 透?jìng)?
默認(rèn)的情況下 ref 是無(wú)法被進(jìn)行向下傳遞的因?yàn)?ref 是特殊的屬性就和 key 一樣不會(huì)被添加到 props 中, 因此 React 提供了一個(gè) API 來(lái)實(shí)現(xiàn)透?jìng)?ref 的這種需求.
這個(gè) API 就是 React.forwardRef.
這個(gè)方法接收一個(gè)函數(shù)返回一個(gè)組件, 在這個(gè)含中它可以讀取到組件傳入的 ref , 某種意義上 React.forwardRef 也相當(dāng)于一個(gè)高階組件:
const ReturnedCompoent = React.forwardRef((props, ref) => { // 我們可以獲取到在props中無(wú)法獲取的 ref 屬性了 return // 返回這個(gè)需要使用 ref 屬性的組件 });
我們把這個(gè) API 用在之前的 HOC 中:
function ExampleHoc(Wrap) { class Inner extends React.Component { render() { const { forwardedRef,...rest} = this.props; return// 2. 我們接收到 props 中被改名的 ref 然后綁定到 ref 上 } } return React.forwardRef((props,ref)=>{ // 1. 我們接收到 ref 然后給他改名成 forwardedRef 傳入到props中 return }) }
這個(gè)時(shí)候在調(diào)用 echo 就沒(méi)有問(wèn)題了:
handleEcho = () => { ref.current.echo(); }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/109694.html
摘要:作用是給組件增減屬性。如果你的高階組件不需要帶參數(shù),這樣寫(xiě)也是很的。那么需要建立一個(gè)引用可以對(duì)被裝飾的組件做羞羞的事情了,注意在多個(gè)高階組件裝飾同一個(gè)組件的情況下,此法并不奏效。你拿到的是上一個(gè)高階組件的函數(shù)中臨時(shí)生成的組件。 是什么 簡(jiǎn)稱(chēng)HOC,全稱(chēng) High Order Component。作用是給react組件增減props屬性。 怎么用 為什么不先說(shuō)怎么寫(xiě)?恩,因?yàn)槟闫鋵?shí)已經(jīng)用...
摘要:原文鏈接高階組件在中是組件復(fù)用的一個(gè)強(qiáng)大工具。在本文中,高階組件將會(huì)被分為兩種基本模式,我們將其命名為和用附加的功能來(lái)包裹組件。這里我們使用泛型表示傳遞到的組件的。在這里,我們定義從返回的組件,并指定該組件將包括傳入組件的和的。 原文鏈接:https://medium.com/@jrwebdev/... 高階組件(HOCs)在React中是組件復(fù)用的一個(gè)強(qiáng)大工具。但是,經(jīng)常有開(kāi)發(fā)者在...
摘要:與繼承相比,裝飾者是一種更輕便靈活的做法。它只是一種模式,這種模式是由自身的組合性質(zhì)必然產(chǎn)生的。對(duì)比原生組件增強(qiáng)的項(xiàng)可操作所有傳入的可操作組件的生命周期可操作組件的方法獲取反向繼承返回一個(gè)組件,繼承原組件,在中調(diào)用原組件的。 導(dǎo)讀 前端發(fā)展速度非常之快,頁(yè)面和組件變得越來(lái)越復(fù)雜,如何更好的實(shí)現(xiàn)狀態(tài)邏輯復(fù)用一直都是應(yīng)用程序中重要的一部分,這直接關(guān)系著應(yīng)用程序的質(zhì)量以及維護(hù)的難易程度。 本...
摘要:高階函數(shù)我們都用過(guò),就是接受一個(gè)函數(shù)然后返回一個(gè)經(jīng)過(guò)封裝的函數(shù)而高階組件就是高階函數(shù)的概念應(yīng)用到高階組件上使用接受一個(gè)組件返回一個(gè)經(jīng)過(guò)包裝的新組件。靈活性在組合階段相對(duì)更為靈活,他并不規(guī)定被增強(qiáng)組件如何使用它傳遞下去的屬性。 在接觸過(guò)React項(xiàng)目后,大多數(shù)人都應(yīng)該已經(jīng)了解過(guò)或則用過(guò)了HOC(High-Order-Components)和FaCC(Functions as Child ...
摘要:總結(jié)其實(shí),這個(gè)和的思想有很大的淵源,不推薦繼承,而是推薦組合,而就是其中的典范。比如我們寫(xiě)了兩個(gè)個(gè)高階組件,一個(gè)是,一個(gè)是,組件就可以隨意的在和之間隨意切換,而不需要改動(dòng)組件原有代碼。 0x000 概述 高階函數(shù)組件...還是一個(gè)函數(shù),和函數(shù)組件不同的是他返回了一個(gè)完整的組件...他返回了一個(gè)class!??! 0x001 直接上栗子 照常,先寫(xiě)個(gè)App組件,外部傳入一個(gè)theme ...
閱讀 2820·2023-04-25 15:01
閱讀 3079·2021-11-23 10:07
閱讀 3366·2021-10-12 10:12
閱讀 3458·2021-08-30 09:45
閱讀 2196·2021-08-20 09:36
閱讀 3587·2019-08-30 12:59
閱讀 2436·2019-08-26 13:52
閱讀 934·2019-08-26 13:24