代理以及繼承方式的高階組件

上一篇文章介紹了簡(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)想要的樣式。

image.png


二、繼承方式的高階組件

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

image.png

  • 兩者繼承的類不同。代理方式的高階組件繼承自 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ì)很麻煩。


image.png

修改方法
這時(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
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • React進(jìn)階之高階組件 前言 本文代碼淺顯易懂,思想深入實(shí)用。此屬于react進(jìn)階用法,如果你還不了解react...
    流動(dòng)碼文閱讀 1,230評(píng)論 0 1
  • 在目前的前端社區(qū),『推崇組合,不推薦繼承(prefer composition than inheritance)...
    Wenliang閱讀 78,047評(píng)論 16 124
  • 前言 學(xué)習(xí)react已經(jīng)有一段時(shí)間了,期間在閱讀官方文檔的基礎(chǔ)上也看了不少文章,但感覺對(duì)很多東西的理解還是不夠深刻...
    Srtian閱讀 1,753評(píng)論 0 7
  • 前端開發(fā)面試題 面試題目: 根據(jù)你的等級(jí)和職位的變化,入門級(jí)到專家級(jí),廣度和深度都會(huì)有所增加。 題目類型: 理論知...
    怡寶丶閱讀 2,683評(píng)論 0 7
  • 高階組件是react應(yīng)用中很重要的一部分,最大的特點(diǎn)就是重用組件邏輯。它并不是由React API定義出來的功能,...
    叫我蘇軾好嗎閱讀 976評(píng)論 0 0

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