React 高階組件HOC用法歸納
一句話介紹HOC
何為高階組件(HOC),根據官方文檔的解釋:“高階組件是react中復用組件邏輯的一項高級技術。它不屬於react API的組成部分,它是從react自身組合性質中抽離出來的一種模式。具體來說,高階組件是函數,它接受一個組件作為參數,然後返回一個新的組件
使用場景
將幾個功能相似的組件裡面的方法和react特性(如生命周期裡面的副作用)提取到HOC中,然後向HOC傳入需要封裝的組件。最後將公用的方法傳給組件。
優勢
使代碼簡潔優雅、代碼量更少
HOC(高階組件)
/* HOC(高階組件): 接收一個組件,返回包裝後的組件(增強組件) - 不是React API - 是一種設計模式,類似於裝飾器模式 - ≈ 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} />; } }; } // 返回函數式組件 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,可以接受任意參數 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嵌套,函數柯裡化的原理 // Hoc1: 給組件添加title屬性 export default function Hoc1(WrappedComponent, title) { return class extends React.Component { render() { return <WrappedComponent title={title} {...this.props} /> } }; }; // Hoc2: 修改組件的顯示內容 export default function Hoc2(WrappedComponent, content) { return class extends WrappedComponent { // 這裡用瞭反向繼承 render() { const elementTree = super.render(); // React用Js對象來模擬Dom樹結構,可以通過修改Js對象的屬性來操縱數據 console.log(elementTree); // 不太瞭解裡面的結構可以打印出來 + 官網cloneElement() 瞭解一下 const newElementTree = React.cloneElement(elementTree, { children: `你的內容已被劫持: ${content}` }); return newElementTree; } }; }; // 被包裹的組件 export default class Header extends React.Component { render() { const { title } = this.props; return ( <span title={title}> 默認內容 </span> ) } }; // 使用 import Hoc1 from './Hoc1'; import Hoc2 from './Hoc2'; /* 包裝過程 1. const Wrapper = Hoc2(Header, '內容'); 2. Hoc1(Wrapper) ** const EnhanceHeader = Hoc1(Hoc2(Header, '內容'), '標題'); export default function Home() { return ( <div> <EnhanceHeader /> </div> ); }; * */
處理ref
/* 處理ref e.g. Hoc1(Hoc2(Content)) <Content ref={myRef} /> 給Content綁定的ref會綁定到Hoc1上,且不會繼續向下傳遞 第一種方法 React.forwardRef =============== 在 Hoc1外面 用React.forwardRef()對ref做處理,用props來傳遞ref 0. 在高階組件外面包裹forwardRef,攔截獲取ref,增加一個props(xxx={ref}),真實組件通過props.xxx獲取 1. 使用時傳 ref={XXXX} // 和第二種方法不同的地方 2. 用forwardRef的第二個參數獲取 ref 3. 增加一個新的props,用來向下轉發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的第二個入參可以接收ref,在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); * */
使用被包裝組件的靜態方法
/* 使用被包裝組件的靜態方法 // 被包裝組件,增加靜態屬性和方法 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包裝後的組件拿不到靜態方法 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:拷貝靜態方法到HOC上 export default function Hoc(WrappedComponent) { return class Wrapper extends React.Component { static displayName = WrappedComponent.displayName; // 必須知道被包裝組件中有什麼靜態方法 static showName = WrappedComponent.showName; render() { return <WrappedComponent {...this.props} /> } }; }; ============== // 解決方法2:自動拷貝所有靜態屬性和方法 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:導出組件時,額外導入靜態屬性和方法 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 } // 導入時 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內部定義,需要註入到被包裝組件的額外的屬性或方法 const injectProps = some-state-or-method; // 通常是state或實例方法 // 為被包裝組件傳遞上層的props + 額外的props return ( <WrappedComponent injectProps={injectProps} // 傳遞需要註入的額外props {...forOtherProps} // 透傳與後續相關的props /> ) } } } e.g. Hoc接收一個額外的props 'dealUpper',如果為true,將data轉換成大寫 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} /> } }; }; // 導出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); // 導出包裝後的增強組件 // 導入使用 import Header from './header'; const Home = () => { return <Header data={'abc'} dealUpper /> } * */
用HOC提取一些復雜的公共邏輯,在不同組件中擴展不同的功能
/* 用HOC提取一些復雜的公共邏輯,在不同組件中擴展不同的功能 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`, // 動態請求不同的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 }) => { ... 省略請求數據的邏輯 return (data.map(item => item)); } export default MyHoc(A, 'a'); // 包裝B組件 import Hoc from './Hoc'; const B = ({ data }) => { ... 省略請求數據的邏輯 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中使用被包裝組件內部的狀態和方法) - 反向繼承的組件要是類組件,函數組件不行 export const Hoc = (WrappedComponent) => { class Wrapper extends WrappedComponent { // super ≈ WrappedComponent裡面的this render() { if (!this.props.data) { return <span>loading....</span> } else { return super.render() // 調用被包裝組件的render()方法 } } } } ==== export default function Hoc(WrappedComponent) { return class extends WrappedComponent { render() { const elementTree = super.render(); // React用Js對象來模擬Dom樹結構,可以通過修改Js對象的屬性來操縱數據 console.log(elementTree); // 不太瞭解裡面的結構可以打印出來 + 官網cloneElement() 瞭解一下 const newElementTree = React.cloneElement(elementTree, { children: `你的內容已被劫持` }); return newElementTree; } }; }; * */
渲染劫持
/* 渲染劫持 e.g. 控制組件是否渲染(可以做全局的loading效果,沒有數據時顯示loading...) // 基本的實現 export const LoadingHoc = (WrappedComponent) => { class Wrapper extends React.Component { render() { if (!this.props.data) { return <span>loading....</span> } else { return <WrappedComponent {...this.props} /> } } } } // 用反向繼承實現 export const LoadingHoc = (WrappedComponent) => { class Wrapper extends WrappedComponent { // super ≈ WrappedComponent裡面的this render() { if (!this.props.data) { return <span>loading....</span> } else { return super.render() // 調用被包裝組件的render()方法 } } } } ====== e.g. 劫持渲染的內容 export default function Hoc2(WrappedComponent) { return class extends WrappedComponent { // 這裡用瞭反向繼承 render() { const elementTree = super.render(); // React用Js對象來模擬Dom樹結構,可以通過修改Js對象的屬性來操縱數據 console.log(elementTree); // 不太瞭解裡面的結構可以打印出來 + 官網cloneElement() 瞭解一下 const newElementTree = React.cloneElement(elementTree, { children: `你的內容已被劫持` }); return newElementTree; } }; }; * */
配置包裝名
/* 配置包裝名:在調試工具 React Developer Tools 中更容易被找到 e.g. 高階組件為Hoc,被包裝組件為WrappedComponent, 顯示的名字應該是 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} />; } }; } // 返回函數式組件 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都會創建一個新的Wrapper // Wrapper1 !== Wrapper2 // 導致高階組件會卸載和重新掛載,狀態會丟失(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 - 屬於函數式編程思想 - 被包裹組件感知不到高階組件的存在 - 高階組件返回的組件會在原來的基礎上的到增強 Mixin - 混入模式,會在被包裝組件上不斷增加新的屬性和方法 - 被包裹組件可感知 - 需要做處理(命名沖突、狀態維護) * */
以上就是React 高階組件HOC用法歸納的詳細內容,更多關於React 高階組件HOC的資料請關註WalkonNet其它相關文章!