函數(shù)式編程-高階組件在前端的應(yīng)用

前言

之前開(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;
反向繼承控制了傳入頁(yè)面的state以及方法
(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;

可以根據(jù)傳入的json去生成對(duì)應(yīng)的頁(yè)面
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);
打印出組件渲染時(shí)間
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);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容