React 高階組件HOC用法歸納
一句話介紹HOC
何為高階組件(HOC),根據(jù)官方文檔的解釋:“高階組件是react中復(fù)用組件邏輯的一項高級技術(shù)。它不屬于react API的組成部分,它是從react自身組合性質(zhì)中抽離出來的一種模式。具體來說,高階組件是函數(shù),它接受一個組件作為參數(shù),然后返回一個新的組件
使用場景
將幾個功能相似的組件里面的方法和react特性(如生命周期里面的副作用)提取到HOC中,然后向HOC傳入需要封裝的組件。最后將公用的方法傳給組件。
優(yōu)勢
使代碼簡潔優(yōu)雅、代碼量更少
HOC(高階組件)
/* HOC(高階組件): 接收一個組件,返回包裝后的組件(增強組件) - 不是React API - 是一種設(shè)計模式,類似于裝飾器模式 - ≈ Mixin && > Minxin const 包裝后的組件 = 高階組件(被包裝的組件); // e.g. const Wrapper = withRouter(NavBar); 高階組件會把所有接收到的props,傳遞給被包裝的組件(透傳) ref 和 key 類似,不是一個prop,所以不會透傳,ref會綁定到外層的包裝容器上 | 解決方法可以參考下面的 <<處理ref>> * */
怎樣包裝組件?
/* 怎樣包裝組件? 第一種: 普通包裝 export時就包裝 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; * */
定義一個簡單的HOC
/* 定義一個簡單的HOC,接收一個組件,返回一個組件 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} /> } }; }; // 包裝時傳參 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對象來模擬Dom樹結(jié)構(gòu),可以通過修改Js對象的屬性來操縱數(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}> 默認內(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)容'), '標題'); export default function Home() { return ( <div> <EnhanceHeader /> </div> ); }; * */
處理ref
/* 處理ref e.g. Hoc1(Hoc2(Content)) <Content ref={myRef} /> 給Content綁定的ref會綁定到Hoc1上,且不會繼續(xù)向下傳遞 第一種方法 React.forwardRef =============== 在 Hoc1外面 用React.forwardRef()對ref做處理,用props來傳遞ref 0. 在高階組件外面包裹forwardRef,攔截獲取ref,增加一個props(xxx={ref}),真實組件通過props.xxx獲取 1. 使用時傳 ref={XXXX} // 和第二種方法不同的地方 2. 用forwardRef的第二個參數(shù)獲取 ref 3. 增加一個新的props,用來向下轉(zhuǎn)發(fā)ref e.g. forwardedRef={ref} 4. 真實組件中綁定 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的第二個入?yún)⒖梢越邮誶ef,在Hoc外層對ref做處理 export default React.forwardRef((props, ref) => { const Wrapper = React.memo(Content); // Hoc // forwardRef包裹的是Wrapper // 需要在Wrapper中把ref向下傳遞給真實組件 // Wrapper中增加一個props屬性,把ref對象作為props傳給子組件 return <Wrapper {...props} forwardedRef={ref} />; }); 第二種方法 ========== 0. 使用時就用一個props來保存ref 1. 使用時傳 xxx={ref} // 和第一種方法的不同點 2. 真實組件中綁定 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:自動拷貝所有靜態(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)出組件時,額外導(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)入時 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,對props進行增刪改
/* 攔截傳給被包裝組件的props,對props進行增刪改 export default function Hoc(WrappedComponent) { return class Wrapper extends React.Component { render() { // 過濾一些僅在當前Hoc中使用的props,不進行不必要的透傳 const { forMeProps, forOtherProps } = this.props; // 在該HOC內(nèi)部定義,需要注入到被包裝組件的額外的屬性或方法 const injectProps = some-state-or-method; // 通常是state或?qū)嵗椒? // 為被包裝組件傳遞上層的props + 額外的props return ( <WrappedComponent injectProps={injectProps} // 傳遞需要注入的額外props {...forOtherProps} // 透傳與后續(xù)相關(guān)的props /> ) } } } e.g. Hoc接收一個額外的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包裝后的增強組件 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)出包裝后的增強組件 // 導(dǎo)入使用 import Header from './header'; const Home = () => { return <Header data={'abc'} dealUpper /> } * */
用HOC提取一些復(fù)雜的公共邏輯,在不同組件中擴展不同的功能
/* 用HOC提取一些復(fù)雜的公共邏輯,在不同組件中擴展不同的功能 import React from 'react'; export const Hoc = (WrappedComponent, namespace) => { class Wrapper extends React.Component { state = { data: [] } // 抽離的相同請求方法 componentDidMount = () => { const { dispatch } = this.props; dispatch({ type: `${namespace}/queryData`, // 動態(tài)請求不同的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 }) => { ... 省略請求數(shù)據(jù)的邏輯 return (data.map(item => item)); } export default MyHoc(A, 'a'); // 包裝B組件 import Hoc from './Hoc'; const B = ({ data }) => { ... 省略請求數(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對象來模擬Dom樹結(jié)構(gòu),可以通過修改Js對象的屬性來操縱數(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ù)時顯示loading...) // 基本的實現(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} /> } } } } // 用反向繼承實現(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對象來模擬Dom樹結(jié)構(gòu),可以通過修改Js對象的屬性來操縱數(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都會創(chuàng)建一個新的Wrapper // Wrapper1 !== Wrapper2 // 導(dǎo)致高階組件會卸載和重新掛載,狀態(tài)會丟失(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ù)式編程思想 - 被包裹組件感知不到高階組件的存在 - 高階組件返回的組件會在原來的基礎(chǔ)上的到增強 Mixin - 混入模式,會在被包裝組件上不斷增加新的屬性和方法 - 被包裹組件可感知 - 需要做處理(命名沖突、狀態(tài)維護) * */
以上就是React 高階組件HOC用法歸納的詳細內(nèi)容,更多關(guān)于React 高階組件HOC的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
教你react中如何理解usestate、useEffect副作用、useRef標識和useContext
這篇文章主要介紹了react中如何理解usestate、useEffect副作用、useRef標識和useContext,其實與vue中的ref和reactive一樣,通過useState獲取到的數(shù)據(jù)可以實現(xiàn)組件視圖實時交互,而普通定義的數(shù)據(jù)僅僅在業(yè)務(wù)中使用,需要的朋友可以參考下2022-11-11React immer與Redux Toolkit使用教程詳解
這篇文章主要介紹了React中immer與Redux Toolkit的使用,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-10-10React使用Canvas繪制大數(shù)據(jù)表格的實例代碼
之前一直想用Canvas做表格渲染的,最近發(fā)現(xiàn)了一個很不錯的Canvas繪圖框架Leafer,api很友好就試著寫了一下,文中有詳細的代碼示例供大家參考,感興趣的小伙伴可以自己動手試試2023-09-09詳解React中錯誤邊界的原理實現(xiàn)與應(yīng)用
在React中,錯誤邊界是一種特殊的組件,用于捕獲其子組件樹中發(fā)生的JavaScript錯誤,并防止這些錯誤冒泡至更高層,導(dǎo)致整個應(yīng)用崩潰,下面我們就來看看它的具體應(yīng)用吧2024-03-03使用useMutation和React Query發(fā)布數(shù)據(jù)demo
這篇文章主要為大家介紹了使用useMutation和React Query發(fā)布數(shù)據(jù)demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12