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}>
默認(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會綁定到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() {
// 過濾一些僅在當(dāng)前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中使用react-player 播放視頻或直播的方法
這篇文章主要介紹了React中使用react-player 播放視頻或直播,本文教大家如何使用react框架及創(chuàng)建實例的代碼,本文內(nèi)容簡短給大家介紹的非常詳細,需要的朋友可以參考下2022-01-01
react ant protable自定義實現(xiàn)搜索下拉框
這篇文章主要介紹了react ant protable自定義實現(xiàn)搜索下拉框,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06
react-router-dom入門使用教程(前端路由原理)
這篇文章主要介紹了react-router-dom入門使用教程,主要包括react路由相關(guān)理解,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-08-08

