React 高階組件HOC用法歸納
一句話介紹HOC
何為高階組件(HOC),根據(jù)官方文檔的解釋:“高階組件是react中復(fù)用組件邏輯的一項(xiàng)高級(jí)技術(shù)。它不屬于react API的組成部分,它是從react自身組合性質(zhì)中抽離出來的一種模式。具體來說,高階組件是函數(shù),它接受一個(gè)組件作為參數(shù),然后返回一個(gè)新的組件
使用場(chǎng)景
將幾個(gè)功能相似的組件里面的方法和react特性(如生命周期里面的副作用)提取到HOC中,然后向HOC傳入需要封裝的組件。最后將公用的方法傳給組件。
優(yōu)勢(shì)
使代碼簡(jiǎn)潔優(yōu)雅、代碼量更少
HOC(高階組件)
/* HOC(高階組件): 接收一個(gè)組件,返回包裝后的組件(增強(qiáng)組件) - 不是React API - 是一種設(shè)計(jì)模式,類似于裝飾器模式 - ≈ Mixin && > Minxin const 包裝后的組件 = 高階組件(被包裝的組件); // e.g. const Wrapper = withRouter(NavBar); 高階組件會(huì)把所有接收到的props,傳遞給被包裝的組件(透?jìng)鳎? ref 和 key 類似,不是一個(gè)prop,所以不會(huì)透?jìng)?,ref會(huì)綁定到外層的包裝容器上 | 解決方法可以參考下面的 <<處理ref>> * */
怎樣包裝組件?
/* 怎樣包裝組件? 第一種: 普通包裝 export時(shí)就包裝 import React from 'react'; import Hoc from './Hoc'; class Header extends React.Component { render() { return <span>{ this.props.count }</span> } }; export default Hoc(Header); ========== import后再包裝: import Header from './header'; import Hoc from './Hoc'; const EnhanceHeader = Hoc(Header); const Home = () => { return ( <div> <EnhanceHeader count={1} /> </div> ) } 第二種: 裝飾器包裝,只能在類組件中使用 import React from 'react'; import Hoc from './Hoc'; @Hoc export default class Header extends React.Component { render() { return <span>{ this.props.count }</span> } }; ======= @Hoc class Header extends React.Component { render() { return <span>{ this.props.count }</span> } }; export default Header; * */
定義一個(gè)簡(jiǎn)單的HOC
/* 定義一個(gè)簡(jiǎn)單的HOC,接收一個(gè)組件,返回一個(gè)組件 import React from 'react'; // 返回類組件 export default function Hoc(WrappedComponent) { /* return class extends React.Component {} - 在 React Developer Tools 中展示的名字是 Component return class Wrapper extends React.Component {} - 在 React Developer Tools 中展示的名字是 Wrapper *\ return class extends React.Component { render() { return <WrappedComponent {...this.props} />; } }; } // 返回函數(shù)式組件 export default function Hoc(WrappedComponent) { /* return function(props) {} - 在 React Developer Tools 中展示的名字是 Anonymous return function Wrapper(props) {} - 在 React Developer Tools 中展示的名字是 Wrapper *\ return function Wrapper(props) { return <WrappedComponent {...props} />; }; } * */
給Hoc傳參
/* 給Hoc傳參 // Hoc,可以接受任意參數(shù) export default function Hoc(WrappedComponent, title, user, data) { return class Wrapper extends React.Component { render() { return <WrappedComponent {...this.props} /> } }; }; // 包裝時(shí)傳參 const EnhanceHeader = Hoc(Header, 'title', { name: '霖'}, [1, 2, 3]); * */
Hoc嵌套
/* Hoc嵌套,函數(shù)柯里化的原理 // Hoc1: 給組件添加title屬性 export default function Hoc1(WrappedComponent, title) { return class extends React.Component { render() { return <WrappedComponent title={title} {...this.props} /> } }; }; // Hoc2: 修改組件的顯示內(nèi)容 export default function Hoc2(WrappedComponent, content) { return class extends WrappedComponent { // 這里用了反向繼承 render() { const elementTree = super.render(); // React用Js對(duì)象來模擬Dom樹結(jié)構(gòu),可以通過修改Js對(duì)象的屬性來操縱數(shù)據(jù) console.log(elementTree); // 不太了解里面的結(jié)構(gòu)可以打印出來 + 官網(wǎng)cloneElement() 了解一下 const newElementTree = React.cloneElement(elementTree, { children: `你的內(nèi)容已被劫持: ${content}` }); return newElementTree; } }; }; // 被包裹的組件 export default class Header extends React.Component { render() { const { title } = this.props; return ( <span title={title}> 默認(rèn)內(nèi)容 </span> ) } }; // 使用 import Hoc1 from './Hoc1'; import Hoc2 from './Hoc2'; /* 包裝過程 1. const Wrapper = Hoc2(Header, '內(nèi)容'); 2. Hoc1(Wrapper) ** const EnhanceHeader = Hoc1(Hoc2(Header, '內(nèi)容'), '標(biāo)題'); export default function Home() { return ( <div> <EnhanceHeader /> </div> ); }; * */
處理ref
/* 處理ref e.g. Hoc1(Hoc2(Content)) <Content ref={myRef} /> 給Content綁定的ref會(huì)綁定到Hoc1上,且不會(huì)繼續(xù)向下傳遞 第一種方法 React.forwardRef =============== 在 Hoc1外面 用React.forwardRef()對(duì)ref做處理,用props來傳遞ref 0. 在高階組件外面包裹forwardRef,攔截獲取ref,增加一個(gè)props(xxx={ref}),真實(shí)組件通過props.xxx獲取 1. 使用時(shí)傳 ref={XXXX} // 和第二種方法不同的地方 2. 用forwardRef的第二個(gè)參數(shù)獲取 ref 3. 增加一個(gè)新的props,用來向下轉(zhuǎn)發(fā)ref e.g. forwardedRef={ref} 4. 真實(shí)組件中綁定 ref={props.forwardedRef} const Home = (props) => { const connectRef = useRef(null); return ( <div> <Content ref={connectRef} /> </div> ); }; // 被包裝組件 const Content = (props) => { return ( <div> <input type="password" ref={props.forwardedRef} /> </div> ); }; // forwardRef的第二個(gè)入?yún)⒖梢越邮誶ef,在Hoc外層對(duì)ref做處理 export default React.forwardRef((props, ref) => { const Wrapper = React.memo(Content); // Hoc // forwardRef包裹的是Wrapper // 需要在Wrapper中把ref向下傳遞給真實(shí)組件 // Wrapper中增加一個(gè)props屬性,把ref對(duì)象作為props傳給子組件 return <Wrapper {...props} forwardedRef={ref} />; }); 第二種方法 ========== 0. 使用時(shí)就用一個(gè)props來保存ref 1. 使用時(shí)傳 xxx={ref} // 和第一種方法的不同點(diǎn) 2. 真實(shí)組件中綁定 ref={props.xxx} const Home = (props) => { const connectRef = useRef(null); return ( <div> <Content forwardedRef={connectRef} /> </div> ); }; // 定義高階組件 export const Hoc = (WrappedComponent) => { class Wrapper extends React.Component { render() { return <WrappedComponent {...props} /> } } } // 被包裝的組件 const Content = (props) => { return ( <div> <input type="password" ref={props.forwardedRef} /> </div> ); }; // 包裝過程 export default Hoc(Content); * */
使用被包裝組件的靜態(tài)方法
/* 使用被包裝組件的靜態(tài)方法 // 被包裝組件,增加靜態(tài)屬性和方法 export default class Header extends React.Component { static displayName = 'header'; static showName = () => { console.log(this.displayName); }; render() { return <span>header</span> } }; // HOC export default function Hoc(WrappedComponent) { return class Wrapper extends React.Component { render() { return <WrappedComponent {...this.props} /> } }; }; =========== // Hoc包裝后的組件拿不到靜態(tài)方法 import Header from './header'; import Hoc from './Hoc'; const EnhanceHeader = Hoc(Header); export default function Home() { console.log(EnhanceHeader.displayName); // undefined EnhanceHeader.showName(); // undefined return <EnhanceHeader /> } ============= // 解決方法1:拷貝靜態(tài)方法到HOC上 export default function Hoc(WrappedComponent) { return class Wrapper extends React.Component { static displayName = WrappedComponent.displayName; // 必須知道被包裝組件中有什么靜態(tài)方法 static showName = WrappedComponent.showName; render() { return <WrappedComponent {...this.props} /> } }; }; ============== // 解決方法2:自動(dòng)拷貝所有靜態(tài)屬性和方法 import React from 'react'; import hoistNonReactStatic from 'hoist-non-react-statics'; export default function Hoc(WrappedComponent) { class Wrapper extends React.Component { render() { return <WrappedComponent {...this.props} /> } }; hoistNonReactStatic(Wrapper, WrappedComponent); return Wrapper; }; ============== // 解決方法3:導(dǎo)出組件時(shí),額外導(dǎo)入靜態(tài)屬性和方法 class Header extends React.Component { render() { return <span>header</span> } }; const displayName = 'header'; function showName() { console.log(Header.displayName); }; Header.displayName =displayName; Header.showName = showName; export default Header export { displayName, showName } // 導(dǎo)入時(shí) import Header, { displayName, showName } from './header'; import Hoc from './Hoc'; const EnhanceHeader = Hoc(Header); export default function Home() { console.log(displayName); // header showName(); // header return <EnhanceHeader /> } * */
攔截傳給被包裝組件的props,對(duì)props進(jìn)行增刪改
/* 攔截傳給被包裝組件的props,對(duì)props進(jìn)行增刪改 export default function Hoc(WrappedComponent) { return class Wrapper extends React.Component { render() { // 過濾一些僅在當(dāng)前Hoc中使用的props,不進(jìn)行不必要的透?jìng)? const { forMeProps, forOtherProps } = this.props; // 在該HOC內(nèi)部定義,需要注入到被包裝組件的額外的屬性或方法 const injectProps = some-state-or-method; // 通常是state或?qū)嵗椒? // 為被包裝組件傳遞上層的props + 額外的props return ( <WrappedComponent injectProps={injectProps} // 傳遞需要注入的額外props {...forOtherProps} // 透?jìng)髋c后續(xù)相關(guān)的props /> ) } } } e.g. Hoc接收一個(gè)額外的props 'dealUpper',如果為true,將data轉(zhuǎn)換成大寫 dealUpper只在該Hoc中使用,所以沒必要傳給被包裝的組件 // HOC export default function Hoc(WrappedComponent) { return class Wrapper extends React.Component { render() { const { dealUpper, ...forOtherProps } = this.props; const { data } = forOtherProps; if (dealUpper) { Object.assign(forOtherProps, {data: data.toUpperCase()}) } return <WrappedComponent {...forOtherProps} /> } }; }; // 導(dǎo)出Hoc包裝后的增強(qiáng)組件 import React from 'react'; import Hoc from './Hoc1'; class Header extends React.Component { render() { console.log(this.props); // { data: 'ABC' } return <span>{this.props.data}</span> } }; export default Hoc(Header); // 導(dǎo)出包裝后的增強(qiáng)組件 // 導(dǎo)入使用 import Header from './header'; const Home = () => { return <Header data={'abc'} dealUpper /> } * */
用HOC提取一些復(fù)雜的公共邏輯,在不同組件中擴(kuò)展不同的功能
/* 用HOC提取一些復(fù)雜的公共邏輯,在不同組件中擴(kuò)展不同的功能 import React from 'react'; export const Hoc = (WrappedComponent, namespace) => { class Wrapper extends React.Component { state = { data: [] } // 抽離的相同請(qǐng)求方法 componentDidMount = () => { const { dispatch } = this.props; dispatch({ type: `${namespace}/queryData`, // 動(dòng)態(tài)請(qǐng)求不同的store payload: {}, callback: res => { if (res) { this.setState({ data: res.data }) } } }) } render() { return <WrappedComponent { ...this.props } data={this.state.data} /> } } } // 包裝A組件 import Hoc from './Hoc'; const A = ({ data }) => { ... 省略請(qǐng)求數(shù)據(jù)的邏輯 return (data.map(item => item)); } export default MyHoc(A, 'a'); // 包裝B組件 import Hoc from './Hoc'; const B = ({ data }) => { ... 省略請(qǐng)求數(shù)據(jù)的邏輯 return ( <ul> { data.map((item, index) => { return <li key={index}><{item}/li> } } </ul> ) } export default Hoc(B, 'b'); * */
讓不受控組件變成受控組件
/* 讓不受控組件變成受控組件 // Hoc組件 export default function Hoc(WrappedComponent) { return class Wrapper extends React.Component { state = { value: '' }; onChange = (e) => { this.setState({ value: e.target.value }) }; render() { const newProps = { value: this.state.value, onChange: this.onChange }; return <WrappedComponent {...this.props} {...newProps} /> } }; }; // 普通組件 class InputComponent extends React.Component { render() { return <input {...this.props} /> } } // 包裝 export default Hoc(InputComponent); * */
反向繼承
/* 反向繼承(在Hoc中使用被包裝組件內(nèi)部的狀態(tài)和方法) - 反向繼承的組件要是類組件,函數(shù)組件不行 export const Hoc = (WrappedComponent) => { class Wrapper extends WrappedComponent { // super ≈ WrappedComponent里面的this render() { if (!this.props.data) { return <span>loading....</span> } else { return super.render() // 調(diào)用被包裝組件的render()方法 } } } } ==== export default function Hoc(WrappedComponent) { return class extends WrappedComponent { render() { const elementTree = super.render(); // React用Js對(duì)象來模擬Dom樹結(jié)構(gòu),可以通過修改Js對(duì)象的屬性來操縱數(shù)據(jù) console.log(elementTree); // 不太了解里面的結(jié)構(gòu)可以打印出來 + 官網(wǎng)cloneElement() 了解一下 const newElementTree = React.cloneElement(elementTree, { children: `你的內(nèi)容已被劫持` }); return newElementTree; } }; }; * */
渲染劫持
/* 渲染劫持 e.g. 控制組件是否渲染(可以做全局的loading效果,沒有數(shù)據(jù)時(shí)顯示loading...) // 基本的實(shí)現(xiàn) export const LoadingHoc = (WrappedComponent) => { class Wrapper extends React.Component { render() { if (!this.props.data) { return <span>loading....</span> } else { return <WrappedComponent {...this.props} /> } } } } // 用反向繼承實(shí)現(xiàn) export const LoadingHoc = (WrappedComponent) => { class Wrapper extends WrappedComponent { // super ≈ WrappedComponent里面的this render() { if (!this.props.data) { return <span>loading....</span> } else { return super.render() // 調(diào)用被包裝組件的render()方法 } } } } ====== e.g. 劫持渲染的內(nèi)容 export default function Hoc2(WrappedComponent) { return class extends WrappedComponent { // 這里用了反向繼承 render() { const elementTree = super.render(); // React用Js對(duì)象來模擬Dom樹結(jié)構(gòu),可以通過修改Js對(duì)象的屬性來操縱數(shù)據(jù) console.log(elementTree); // 不太了解里面的結(jié)構(gòu)可以打印出來 + 官網(wǎng)cloneElement() 了解一下 const newElementTree = React.cloneElement(elementTree, { children: `你的內(nèi)容已被劫持` }); return newElementTree; } }; }; * */
配置包裝名
/* 配置包裝名:在調(diào)試工具 React Developer Tools 中更容易被找到 e.g. 高階組件為Hoc,被包裝組件為WrappedComponent, 顯示的名字應(yīng)該是 Hoc(WrappedComponent) // 返回類組件 export default function Hoc(WrappedComponent) { return class extends React.Component { /* 沒有在Hoc中定義 static displayName = 'XXX'; - React Developer Tools 中展示的名字是 Anonymous 沒有在被包裝組件中定義 static displayName = 'XXX'; - React Developer Tools 中展示的名字是 undefined Hoc 在被包裝組件中定義 static displayName = 'header'; - React Developer Tools 中展示的名字是 header Hoc *\ static displayName = `Hoc(${WrappedComponent.displayName}); render() { return <WrappedComponent {...this.props} />; } }; } // 返回函數(shù)式組件 export default function Hoc(WrappedComponent) { /* return function(props) {} - 在 React Developer Tools 中展示的名字是 Anonymous return function Wrapper(props) {} - 在 React Developer Tools 中展示的名字是 Wrapper * return function Wrapper(props) { return <WrappedComponent {...props} />; }; } ======= export default function Hoc(WrappedComponent) { const Wrapper = (props) => { return <WrappedComponent {...props} />; }; /* 沒有在被包裝組件中定義 static displayName = 'XXX'; - React Developer Tools 中展示的名字是 undefined Hoc 在被包裝組件中定義 static displayName = 'header'; - React Developer Tools 中展示的名字是 header Hoc *\ Wrapper.displayName = `Hoc(${WrappedComponent.displayName})`; return Wrapper; } ===== // 被包裹組件 export default class Header extends React.Component { static displayName = 'header'; render() { return <span>{ this.props.count }</span> } }; * */
不要在render中使用HOC
/* 不要在render中使用HOC e.g. export default class Home extends React.Component { render() { // 每次render都會(huì)創(chuàng)建一個(gè)新的Wrapper // Wrapper1 !== Wrapper2 // 導(dǎo)致高階組件會(huì)卸載和重新掛載,狀態(tài)會(huì)丟失(e.g. checkbox的選中丟失 | state被清空) × const Wrapper = Hoc(WrappedComponent); return <Wrapper /> } } ========= √ const Wrapper = myHoc(WrappedComponent); export default class Home extends React.Component { render() { return <Wrapper /> } } * */
Hoc的渲染順序
/* Hoc的渲染順序 Hoc(Header) componentDidMount: Header -> HOC componentWillUnMount: HOC -> Header * */
HOC 和 Mixin
/* HOC 和 Mixin HOC - 屬于函數(shù)式編程思想 - 被包裹組件感知不到高階組件的存在 - 高階組件返回的組件會(huì)在原來的基礎(chǔ)上的到增強(qiáng) Mixin - 混入模式,會(huì)在被包裝組件上不斷增加新的屬性和方法 - 被包裹組件可感知 - 需要做處理(命名沖突、狀態(tài)維護(hù)) * */
以上就是React 高階組件HOC用法歸納的詳細(xì)內(nèi)容,更多關(guān)于React 高階組件HOC的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
React團(tuán)隊(duì)測(cè)試并發(fā)特性詳解
這篇文章主要為大家介紹了React團(tuán)隊(duì)測(cè)試并發(fā)特性詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08教你react中如何理解usestate、useEffect副作用、useRef標(biāo)識(shí)和useContext
這篇文章主要介紹了react中如何理解usestate、useEffect副作用、useRef標(biāo)識(shí)和useContext,其實(shí)與vue中的ref和reactive一樣,通過useState獲取到的數(shù)據(jù)可以實(shí)現(xiàn)組件視圖實(shí)時(shí)交互,而普通定義的數(shù)據(jù)僅僅在業(yè)務(wù)中使用,需要的朋友可以參考下2022-11-11React immer與Redux Toolkit使用教程詳解
這篇文章主要介紹了React中immer與Redux Toolkit的使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-10-10React使用Canvas繪制大數(shù)據(jù)表格的實(shí)例代碼
之前一直想用Canvas做表格渲染的,最近發(fā)現(xiàn)了一個(gè)很不錯(cuò)的Canvas繪圖框架Leafer,api很友好就試著寫了一下,文中有詳細(xì)的代碼示例供大家參考,感興趣的小伙伴可以自己動(dòng)手試試2023-09-09淺談使用React.setState需要注意的三點(diǎn)
本篇文章主要介紹了淺談使用React.setState需要注意的三點(diǎn),提出了三點(diǎn)對(duì) React 新手來說是很容易忽略的地方,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12詳解React中錯(cuò)誤邊界的原理實(shí)現(xiàn)與應(yīng)用
在React中,錯(cuò)誤邊界是一種特殊的組件,用于捕獲其子組件樹中發(fā)生的JavaScript錯(cuò)誤,并防止這些錯(cuò)誤冒泡至更高層,導(dǎo)致整個(gè)應(yīng)用崩潰,下面我們就來看看它的具體應(yīng)用吧2024-03-03使用useMutation和React Query發(fā)布數(shù)據(jù)demo
這篇文章主要為大家介紹了使用useMutation和React Query發(fā)布數(shù)據(jù)demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12