上一篇文章介紹了簡(jiǎn)單的高階組件實(shí)現(xiàn)方式,接下來介紹代理和繼承方式的高階組件。
一、代理方式的高階組件
應(yīng)用場(chǎng)景:
1. 操縱props
高階組件能夠改變被包裹組件的 props ,可以對(duì) props 做任何操作,甚至可以在高階組件中自定義事件,然后通過 props 傳遞下去。
上一篇實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的高階組件,下面介紹如何在高階組件中傳遞被包裹組件的屬性:
App.js
import React from 'react';
import B from './components/B';
import C from './components/C';
import './index.css';
class App extends React.Component {
render() {
return(
<div className="out-box">
{/* 這里傳遞name和age屬性給B組件 */}
<B name={"Tom"} age={22} />
<C />
</div>
)
}
}
export default App;
A.js
import React, { Component } from 'react';
function hocA(Wrapper) {
return class A extends Component {
render() {
return (
<div className="box">
<header className="head">頭部</header>
<div className="content">
<Wrapper />
</div>
</div>
)
}
}
};
export default hocA;
B.js
import React, { Component } from 'react';
// 引入該高階組件
import hocA from './A';
class B extends Component {
render() {
return (
<div>
<div>
姓名:{this.props.name}
</div>
<div>
年齡:{this.props.age}
</div>
<div>
這是B組件
</div>
</div>
)
}
}
export default hocA(B);
-
此時(shí) B 組件的效果
image.png
可以看到 B 組件的 props 中拿不到我們傳遞的 name 和 age 屬性。原因在于我們屬性傳遞到高階組件 A 里面,但是我們 A 組件沒有把屬性傳給被包裹組件,這就導(dǎo)致了被包裹的 B 組件拿不到 name 和 age 屬性。
- 改寫后的
A.js
import React, { Component } from 'react';
function hocA(Wrapper) {
return class A extends Component {
render() {
return (
<div className="box">
<header className="head">頭部</header>
<div className="content">
{/* 將傳遞到高階組件的屬性解構(gòu)出來并傳遞給被包裹屬性 */}
<Wrapper {...this.props} />
</div>
</div>
)
}
}
};
export default hocA;
- 頁(yè)面效果
image.png
如何通過高階組件給被包裹組件增加屬性 A.js
import React, { Component } from 'react';
function hocA(Wrapper) {
return class A extends Component {
render() {
return (
<div className="box">
<header className="head">頭部</header>
<div className="content">
{/* 增加sex屬性 */}
<Wrapper {...this.props} sex={"男"} />
</div>
</div>
)
}
}
};
export default hocA;
- B 組件中使用該屬性,
B.js
import React, { Component } from 'react';
// 引入該高階組件
import hocA from './A';
class B extends Component {
render() {
return (
<div>
<div>
姓名:{this.props.name}
</div>
<div>
年齡:{this.props.age}
</div>
<div>
性別:{this.props.sex}
</div>
<div>
這是B組件
</div>
</div>
)
}
}
export default hocA(B);
- 頁(yè)面效果
image.png
如何通過高階組件刪除被包裹組件的屬性 - 改寫后的
A.js
import React, { Component } from 'react';
function hocA(Wrapper) {
return class A extends Component {
render() {
{/* 將props都解構(gòu)出來,除了age屬性以外的其它屬性都用newProps來存放 */}
const { age, ...newProps } = this.props;
return (
<div className="box">
<header className="head">頭部</header>
<div className="content">
<Wrapper {...newProps} sex={"男"} />
</div>
</div>
)
}
}
};
export default hocA;
-
頁(yè)面效果
image.png
2. 訪問被包裹組件的ref
A.js
import React, { Component } from 'react';
function hocA(Wrapper) {
return class A extends Component {
// instance就是傳入的被包裹組件Wrapper
controlRef(instance) {
instance.getName && instance.getName();
}
render() {
return (
<div className="box">
<header className="head">頭部</header>
<div className="content">
<Wrapper ref={this.controlRef.bind(this)} />
</div>
</div>
)
}
}
};
export default hocA;
B.js
import React, { Component } from 'react';
import hocA from './A';
class B extends Component {
getName() {
console.log('訪問到了');
}
render() {
return (
<div>
<div>
這是B組件
</div>
</div>
)
}
}
export default hocA(B);
此時(shí)我們?nèi)ロ?yè)面控制臺(tái)就可以看到執(zhí)行了被包裹組件 B 里面的 getName 方法了。
3. 抽離狀態(tài)
舉個(gè)例子:
假如我們高階組件包裹的組件都有共同的一個(gè)方法,比如說一個(gè)輸入框,我們希望讓這個(gè)輸入框受控,那么我們就要監(jiān)聽這個(gè)輸入框的 input 事件了,每次輸入值就使用 setState 讓輸入框內(nèi)容也跟著改變。如果我們?cè)诟鱾€(gè)組件中都自己實(shí)現(xiàn)這個(gè)方法,那么就會(huì)造成很多重復(fù)的工作。此時(shí)可以利用高階組件幫我們?nèi)コ殡x狀態(tài)。
A.js
import React, { Component } from 'react';
function hocA(Wrapper) {
return class A extends Component {
constructor(props) {
super(props);
// 把狀態(tài)統(tǒng)一抽離到高階組件里
this.state = {
value: ''
}
}
// 把方法統(tǒng)一抽離到高階組件里
handleChange = (e) => {
this.setState({
value: e.target.value
})
}
render() {
const newProps = {
value: this.state.value,
onChange: this.handleChange
}
return (
<div className="box">
<header className="head">頭部</header>
<div className="content">
{ /* 把狀態(tài)和方法傳給被包裹組件 */ }
<Wrapper {...newProps} />
</div>
</div>
)
}
}
};
export default hocA;
B.js
import React, { Component } from 'react';
import hocA from './A';
class B extends Component {
render() {
return (
<div>
{ /* 讓高階組件幫我們實(shí)現(xiàn)輸入框受控 */ }
<input {...this.props} />
<div>
這是B組件
</div>
</div>
)
}
}
export default hocA(B);
C.js
import React, { Component } from 'react';
import hocA from './A';
// 裝飾器語(yǔ)法使用該高階組件
@hocA
class C extends Component {
render() {
return (
<div>
{ /* 讓高階組件幫我們實(shí)現(xiàn)輸入框受控 */ }
<input {...this.props} />
<div>
這是C組件
</div>
</div>
)
}
}
export default C;
-
頁(yè)面效果
image.png
這樣我們就在高階組件中把公用狀態(tài)抽離出來,提高了代碼的復(fù)用性。
4. 包裝組件
包裝組件很簡(jiǎn)單,我們前面就使用了該特性,所謂包裝組件就是添加一系列標(biāo)簽,讓被包裹組件實(shí)現(xiàn)想要的樣式。

二、繼承方式的高階組件
采用繼承關(guān)聯(lián)作為參數(shù)的組件和返回的組件,假如傳入的組件參數(shù)是Wrapper,那么返回的組件就直接繼承自 Wrapper 。
和代理方式的高階組件的區(qū)別

- 兩者繼承的類不同。代理方式的高階組件繼承自 React.Component,繼承方式的高階組件則是繼承自傳入的參數(shù)組件 。
- render 函數(shù)中 return 出去的東西不同。代理方式的高階組件返回的是傳入的參數(shù)組件,繼承則是返回 super.render(),渲染出參數(shù)組件。
應(yīng)用場(chǎng)景:
1. 操縱props
使用繼承方式的高階組件給參數(shù)組件添加屬性
A.js
import React from 'react';
function hocA(Wrapper) {
return class A extends Wrapper {
render() {
// 拿到參數(shù)組件的元素
const element = super.render();
const newStyle = {
// 如果參數(shù)組件元素的最外層是div標(biāo)簽則返回紅色,否則返回綠色
color: element.type === 'div' ? 'red' : 'green',
};
const newProps = { ...this.props, style: newStyle };
return (
<div className="box">
<header className="head">頭部</header>
<div className="content">
{React.cloneElement(element, newProps, element.props.children)}
</div>
</div>
)
}
}
};
export default hocA;
B.js
import React, { Component } from 'react';
import hocA from './A';
class B extends Component {
render() {
return (
<div>
這是B組件
</div>
)
}
}
export default hocA(B);
C.js
import React, { Component } from 'react';
import hocA from './A';
// 裝飾器語(yǔ)法使用該高階組件
@hocA
class C extends Component {
render() {
return (
<span>
這是組件C
</span>
)
}
}
export default C;
-
效果
image.png
實(shí)際過程中除非需要通過傳入的參數(shù)組件來判斷性地去修改一些屬性,否則沒有必要使用繼承方式高階組件去操縱props。
2. 操縱生命周期函數(shù)
繼承方式的高階組件需要修改生命周期函數(shù)時(shí)直接在高階組件內(nèi)重寫生命周期函數(shù)即可,它會(huì)覆蓋掉參數(shù)組件的生命周期函數(shù)。
從上面可以看出來,代理方式的高階組件要優(yōu)于繼承方式的高階組件,所以優(yōu)先使用代理方式的高階組件。
三、修改顯示的組件名
打開組件調(diào)試工具,可以看到組件B和組件C重名了,都是A,如果組件特別多的話,調(diào)試起來會(huì)很麻煩。

修改方法
這時(shí)候需要用到 react 類里面的靜態(tài)屬性 displayName,用于設(shè)置顯示的組件名稱。
A.js
import React, { Component } from 'react';
function hocA(Wrapper) {
return class A extends Component {
// 設(shè)置顯示高階組件顯示名稱
static displayName = `Box${getDisplayName(Wrapper)}`;
render() {
return (
<div className="box">
<header className="head">頭部</header>
<div className="content">
<Wrapper />
</div>
</div>
)
}
}
};
function getDisplayName(Wrapper) {
return Wrapper.displayName || Wrapper.name || 'defaultName'
}
export default hocA;
-
效果
image.png






