前言
之前開(kāi)發(fā)項(xiàng)目寫過(guò)一些裝飾器覺(jué)得很不錯(cuò),比如全局loading的應(yīng)用,還有一個(gè)頁(yè)面上多種彈窗造成頁(yè)面的state過(guò)于繁重,維護(hù)很困難,因此抽離出各種公共組件便于維護(hù),代碼也會(huì)縮短很多,因此寫篇文章深入的記錄一下
一、什么是高階組件 目的是什么
官方解釋是:一個(gè)傳入一個(gè)組件,返回另一個(gè)組件的函數(shù),其概念與高階函數(shù)的將函數(shù)作為參數(shù)傳入類似。
使用目的
- 將高度相似的部分抽離出來(lái),比如一個(gè)常用組件,比如一個(gè)彈窗,可能有不同顏色的彈窗,或者只是某些小的地方不同,這樣抽離抽離出來(lái)便于前端代碼的維護(hù)
- 生命周期 state 的捕獲 渲染劫持
二、使用方法
1.簡(jiǎn)單的裝飾器
- 取到或操作原組件的props?
- 能否取到或操作state?
- 能否通過(guò)ref訪問(wèn)到原組件中的dom元素?
- 是否影響原組件生命周期等方法?
- 是否取到原組件static方法?
- 劫持原組件生命周期?
- 渲染劫持?
// common.js 公共抽離部分
import React from 'react';
const common = WrapperComponet => class extends WrapperComponet {
constructor (props) {
super(props)
this.state = {
...this.state,
list: [],
num: 1
}
}
onClick = () => {
this.setState({ num: this.state.num + 3 });
}
render () {
const newProps = {a: 1};
return <WrapperComponet onClick={this.onClick} { ...newProps}/>
)
}
}
//頁(yè)面
import React from 'react';
import './App.css';
import common from './common';
@common
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
}
}
render () {
console.log(this.props); // {onClick: ?, a: 1} 裝飾器里面可以對(duì)傳入的類進(jìn)行一些更改
return (
<div className="App">
點(diǎn)擊 {this.state.num}
</div>
);
}
}
export default App;
2.利用高階組件的反向繼承,操作state 方法等
不太推薦利用操作反向繼承操作state 這樣會(huì)和原組件造成沖突,最好的應(yīng)用場(chǎng)景是調(diào)試組件,在里面寫一些調(diào)試代碼
- 取到或操作原組件的props?
- 能否取到或操作state?
- 能否通過(guò)ref訪問(wèn)到原組件中的dom元素?
- 是否影響原組件生命周期等方法?
- 是否取到原組件static方法?
- 劫持原組件生命周期?
- 渲染劫持?
反向繼承最核心的兩個(gè)作用,一個(gè)是渲染劫持,另一個(gè)是覆蓋原有的state
(1)覆蓋原有state
const common = WrapperComponet => class extends WrapperComponet {
constructor (props) {
super(props)
this.state = {
...this.state,
list: [],
num: 1
}
}
onClick = () => { //被覆蓋
this.setState({ num: 100 });
}
render () {
// return <WrapperComponet onClick={this.onClick} { ...newProps}/>
const elementsTree = super.render();
const { children, ...otherProps } = elementsTree.props;
return React.cloneElement(
elementsTree,
otherProps,
...children,
)
}
}
@common
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
}
}
onClick = () => {
this.setState({ num: 99 });
}
render () {
return (
<div className="App" onClick={this.onClick}>
點(diǎn)擊 {this.state.num}
</div>
);
}
}
export default App;
(2)渲染劫持
render函數(shù)實(shí)際是利用react.createElement產(chǎn)生元素 我們可以拿到它 但是我們不能對(duì)其進(jìn)行更改 ,render 函數(shù)應(yīng)該是一個(gè)純函數(shù),完全根據(jù) this.state 和this.props 來(lái)決定返回的結(jié)果,而且不要產(chǎn)生任何副作用。在 render 函數(shù)中去調(diào)用 this.setState 毫無(wú)疑問(wèn)是錯(cuò)誤的,因?yàn)橐粋€(gè)純函數(shù)不應(yīng)該引起狀態(tài)的改變。 我們只能通過(guò)react.cloneElement對(duì)其進(jìn)行增強(qiáng)。
const HOC = (WrappedComponent) => {
return class extends WrappedComponent {
render() {
const tree = super.render();
let newProps = {};
if (tree && tree.type === 'input') {
newProps = { value: 'value被劫持' };
}
const props = { ...tree.props, ...newProps };
const newTree = React.cloneElement(tree, props, tree.props.children);
return newTree;
}
}
}
export default HOC;
三、修飾器幾種類型
1、裝飾一個(gè)類的屬性
function log(target, key, descriptor) {
console.log(target);
console.log(target.hasOwnProperty('constructor'));
console.log(target.constructor);
console.log(key);
console.log(descriptor);
}
class Bar {
@log;
bar() {}
}
// {}
// true
// function Bar() { ...
// bar
// {"enumerable":false,"configurable":true,"writable":true}
readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}
class cat {
@readonly
say() {
console.log('cat');
}
}
var kitty = new Cat();
kitty.say = function() {
console.log("dog");
}
kitty.say() // cat
同時(shí) 我們也可以在target上進(jìn)行裝飾
class Bar {
@log;
bar() {}
}
log(target, name, descriptor){
target.speed = 20;
let run = descriptor.value;
descriptor.value = function(){
run();
console.log(`speed${this.speed}`);
}
return descriptor;
}
var a = new Bar();
a.speed // 20
a.bar(); // speed20
以上修飾原理使用的
let descriptor = {
value: function() {
console.log("meow ~");
},
enumerable: false,
configurable: true,
writable: true
};
descriptor = readonly(Cat.prototype, "say", descriptor) || descriptor;
Object.defineProperty(Cat.prototype, "say", descriptor);
2、裝飾一個(gè)類
@fruit
class Lala() {}
fruit(item) {
item.apple = 1;
return item;
}
Lala.apple // 1
裝飾類對(duì)本身操作 裝飾類的屬性對(duì)描述符descriptor進(jìn)行操作
4、高階組件的應(yīng)用
1.頁(yè)面復(fù)用(工廠模式)
比如一個(gè)公共頁(yè)面 只是某些字段發(fā)生改變,可以將這個(gè)公共頁(yè)面設(shè)計(jì)成工廠
(高階組件),外部傳入一個(gè)json配置給這個(gè)裝飾器的參數(shù),下面舉例一個(gè)簡(jiǎn)單的??
import shopList from './shopList';
const defaultProps = {
fetchData: () => {console.log('fetch some data');},
labelName: 'title',
value: '123'
};
const App = shopList(defaultProps);
export default App;
import React from 'react';
function CommonPage(config) {
const {
fetchData,
labelName,
value
} = config;
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
};
}
render() {
return <div onClick={fetchData}>
{`${labelName}: ${value}`}
</div>
}
};
}
export default CommonPage;
2.頁(yè)面的選擇渲染
比如 一個(gè)頁(yè)面如果根據(jù)權(quán)限去渲染一些不同的頁(yè)面 而且這個(gè)判斷設(shè)計(jì)很多頁(yè)面 那么我們不能將這些判斷都寫在代碼中 重復(fù)的邏輯應(yīng)該抽離出出來(lái)
import AuthPage from './two';
class App extends React.Component {
componentWillMount() {
// 獲取業(yè)務(wù)數(shù)據(jù)
}
render() {
return <div>業(yè)務(wù)頁(yè)面</div>;
}
}
export default AuthPage(App);
import React from 'react';
const AuthPage = WrappedComponent => class extends React.Component {
constructor(props) {
super(props);
this.state = {
permission: -1,
};
}
componentWillMount() {
// 權(quán)限獲取接口,在此模擬promise
new Promise(resolve => resolve(0)).then((res) => {
// success
this.setState({
permission: res,
});
});
}
render() {
if (this.state.permission) {// 非0顯示特殊頁(yè)面
return <div>特殊頁(yè)面</div>;
}
return <WrappedComponent {...this.props} />;
}
}
export default AuthPage;
3.頁(yè)面的性能指標(biāo)監(jiān)控
對(duì)某些頁(yè)面進(jìn)行時(shí)間監(jiān)控 利用高階組件防止重復(fù)代碼
import React from 'react';
function Performance(WrappedComponent) {
return class extends WrappedComponent {
constructor(props) {
super(props);
this.start = 0;
this.end = 0;
}
componentWillMount() {
super.componentWillMount && super.componentWillMount();
this.start = Date.now();
}
componentDidMount() {
super.componentDidMount && super.componentDidMount();
this.end = Date.now();
console.log(`組件渲染時(shí)間為 ${this.end - this.start} ms`);
}
render() {
return super.render();
}
};
}
export default Performance;
import Performance from './three';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
}
}
componentWillMount() {
// 獲取業(yè)務(wù)數(shù)據(jù)
}
render() {
return <div>業(yè)務(wù)頁(yè)面</div>;
}
}
export default Performance(App);
4.對(duì)組件進(jìn)行二次封裝
點(diǎn)擊按鈕希望請(qǐng)求未回來(lái)的時(shí)候顯示loading狀態(tài)防止二次請(qǐng)求
import Button from './Button';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false
}
}
componentWillMount() {
// 獲取業(yè)務(wù)數(shù)據(jù)
}
onClick = () => {
// 模擬一個(gè)接口
return new Promise(resolve => {
setTimeout(() => {
resolve(4);
}, 4000);
}).then((res) => {
console.log(res, '業(yè)務(wù)代碼');
});
}
render() {
return <Button type="primary" onClick={this.onClick}>請(qǐng)求</Button>;
}
}
export default App;
// ButtonWrapper.js
import React from 'react';
const Button = WrappedComponent => class extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false
};
}
componentWillMount() {
}
handleClick = () => {
// 只有return一個(gè)promise才會(huì)加載loading狀態(tài),否則正常執(zhí)行,因?yàn)榕袛嗍欠裼衪hen必須還要執(zhí)行一遍onclick,所以暫時(shí)只能加try catch
try {
this.setState({ loading: true });
this.props.onClick().then((res) => {
this.setState({ loading: false });
});
}
catch(e) {
this.setState({ loading: false });
}
}
render() {
return <WrappedComponent {...this.props} loading={this.state.loading} onClick={this.handleClick}/>;
}
}
export default Button;
import { Button } from 'antd';
import ButtonWrapper from './ButtonWrapper';
export default ButtonWrapperewButton(Button);