「React」之組件邏輯複用小技巧

編者薦語:

本文將介紹React組件邏輯複用的一些經常使用模式和技巧。包括一下幾個方面:javascript

  • 什麼是高階組件 HOC
  • HOC解決了哪些問題
  • 如何封裝一個簡單的高階組件
  • HOC在項目中經常使用的一些技巧和方法
  • 什麼是 Render Props
  • Render Props的特色和用法
  • Render PropsHOC React Hooks相比,有哪些優劣(重要面試題)

HOC高階組件

高階組件(HOC):是React中用於複用組件邏輯的一種高級技巧HOC自身不是React API的一部分,它是一種基於React的組合特性而造成的設計模式前端

高階組件能夠看作React裝飾器模式的一種實現,具體而言,高階組件是參數做爲組件,返回值爲新組件的函數java

HOC解決的問題

  • 抽離公共組件,實現組件代碼複用,常見場景:頁面複用。
  • 條件渲染,控制組件的渲染邏輯(渲染劫持),常見場景:權限控制。
  • 捕獲/劫持被處理組件的生命週期,常見場景:組件渲染性能追蹤、日誌打點。

當咱們項目中使用高階組件開發時,可以讓代碼變得更加優雅,同時加強代碼的複用性和靈活性,提高開發效率react

高階組件的基本框架

高階組件的框架:web

export default (WrappedComponent) => {
 return class NewComponent extends React.Component {
  // 能夠自定義邏輯
    // 好比給 WrappedComponent組件傳遞props和methods
    render () {
   return <WrappedComponent {...this.props}/>
    }
  }
}

若是自定義了statemethods能夠經過下面方式傳遞到子組件中面試

export default (WrappedComponent) => {
 return class NewComponent extends React.Component {
  state = {
      markTimenew Date().toLocaleTimeString(); // 獲取組件當前渲染時的時間
    }
    printTime() {
      let myDate = new Date();
      let myTime= myDate.toLocaleTimeString(); 
      console.log('當前時間', myTime)
    }
    render () {
   return <WrappedComponent markTime={this.state.markTime} printTime={this.printTime}/>
    }
  }
}

這樣在WrappedComponent組件中,若是是類組件就能夠經過this.props.markTime獲取,函數組件的話經過props.markTime來獲取,方法獲取和狀態獲取相同。設計模式

HOC能夠作什麼

屬性代理——可操做全部傳入的props

能夠讀取、添加、編輯、刪除傳給 WrappedComponent 的 props(屬性)數組

「場景描述」:Hello組件傳遞show,hide方法,讓其顯示Loading加載框性能優化

const loading = message => OldComponent => {
  return class extends React.Component {
    // 顯示一個 Loading的div
    state = {
      show() => {
        let div = document.createElement('div');
        div.innerHTML = `<p id="loading" style="position: absolute; z-index:10; top: 10; color: red; border: 1px solid #000">${message}</p>`
        document.body.appendChild(div);
      },
      hide() => {
        document.getElementById('loading').remove();
      }
    }
    render() {
      return (
        <div>
          <OldComponent {...this.props} {...this.state}/>
        </div>
      )
    }
  }
}
function Hello(props) {
  return (
    <div>hello
      <button onClick={props.show}>show</button>
      <button onClick={props.hide}>hide</button>
    </div>
  )
}

let HightLoadingHello = loading('正在加載')(Hello);
ReactDom.render(<HightLoadingHello/>, document.getElementById('root'));

效果如圖:微信

抽離公共組件,最大化實現複用

「場景描述」: 統計每一個組件的渲染時間

class CalTimeComponent extends React.Component {
  componentWillMount() {
    this.start = Date.now(); // 初始渲染節點
  }
  componentDidMount() {
    console.log((Date.now() - this.start) + 'ms');
  }
  render() {
    return <div>calTimeComponent</div>
  }
}

ReactDom.render(<CalTimeComponent/>document.getElementById('root'));

這樣僅僅能計算當前組件的渲染時間,假如如今有這樣一個需求,須要統計每一個組件的渲染時間呢?

就應該想到把它抽離出去,好比:

// CalTimeComponent.js
export default function CalTimeComponent(OldComponent{
  return class extends React.Component {
    state = {
      markTimenew Date().toLocaleTimeString()
    }
    componentWillMount() {
      this.start = Date.now();
    }
    componentDidMount() {
      console.log((Date.now() - this.start) + 'ms');
    }
    printTime() {
      let myDate = new Date();
      let myTime= myDate.toLocaleTimeString(); 
      console.log('當前時間', myTime)
    }
    render() {
      return <OldComponent markTime={this.state.markTime} printTime={this.printTime}/>
    }
  }
}

// HelloComponent.js
import withTracker from '../../Components/CalTimeComponent.js';

class HelloComponent extends React.Component{
  render() {
    console.log(this.props);
    this.props.printTime()
    return <div>hello</div>
  }
}

let HighHelloComponent = CalTimeComponent(HelloComponent);
ReactDom.render(<HighHelloComponent/>, document.getElementById('root'));

這樣就能最大化的實現CalTimeComponent組件複用了,把它引入到想要計算時間的組件裏,並傳入當前組件就行了。

Render Props

特色1render props指在一種React組件之間使用一個值爲函數的props共享代碼的簡單技術。

特色2:具備render props的組件接收一個函數,該函數返回一個React元素並調用它而不是實現一個本身的渲染邏輯。

特色3render props是一個用於告知組件須要渲染什麼內容的函數(props)

特色4:也是組件邏輯複用的一種實現方式

接下來,我經過一個例子帶你們分別認識上面的四種特色

「場景描述」: 在多個組件內實時獲取鼠標的x、y座標

原生實現:不復用邏輯

class MouseTracker extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      x0,
      y0,
    }
  }
  handleMouseMove = (event) => {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }
  render() {
    return (
      <div onMouseMove={this.handleMouseMove}>
        <h1>請移動鼠標</h1>
        <p>當前鼠標的位置是:x:{this.state.x} y:{this.state.y}</p>
      </div>

    )
  }
}

ReactDom.render(<MouseTracker/>document.getElementById('root'));

上面,這是在一個組件內完成的,假如如今要在多個div內完成上面的邏輯該怎麼辦,就該想到複用了,看看render prop是怎麼幫咱們完成的?

Render Props

class MouseTracker extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      x0,
      y0,
    }
  }
  handleMouseMove = (event) => {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }
  render() {
    console.log(this.props)
    return (
      <div onMouseMove={this.handleMouseMove}>
        {this.props.render(this.state)}
      </div>

    )
  }
}

ReactDom.render(
<MouseTracker render={
  props =>
 (
    <React.Fragment>
      <h1>請移動鼠標</h1>
      <p>當前鼠標的位置是: x:{props.x} y:{props.y}</p>
    </React.Fragment>
  )
}></MouseTracker>
document.getElementById('root'));

注意:render props 是由於模式才被稱爲 render props ,你不必定要用名爲 renderprops 來使用這種模式。render props 是一個用於告知組件須要渲染什麼內容的函數 `prop

那若是改寫成高階組件呢?

高階組件寫法

改寫成高階組件,並將公共組件抽離出去, ShowPosition子組件中能夠拿到withTracker父組件中傳遞的x、y座標值

// withTracker.js
export default function withTracker (OldComponent{
  return class MouseTracker extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        x0,
        y0,
      }
    }
    handleMouseMove = (event) => {
      this.setState({
        x: event.clientX,
        y: event.clientY
      });
    }
    render() {
      console.log(this.props)
      return (
        <div onMouseMove={this.handleMouseMove}>
          <OldComponent {...this.state}/>
        </div>
      )
    }
  }
}

// ShowPosition.js
import withTracker from '../../Components/withTracker.js';

function ShowPosition(props) {
  return (
    <React.Fragment>
      <h1>請移動鼠標</h1>
      <p>當前鼠標的位置是: x:{props.x} y:{props.y}</p>
    </React.Fragment>
  )
}

// 在 ShowPosition 組件中 能夠拿到 withTracker 傳遞過來的座標值
let HightShowPosition = withTracker(ShowPosition);

ReactDom.render(<HightShowPosition/>, document.getElementById('root'));

hoc、render props、react-hooks的優劣如何?

HOC的優點:

  • 抽離公共組件,實現組件代碼複用,常見場景:頁面複用。
  • 條件渲染,控制組件的渲染邏輯(渲染劫持),常見場景:權限控制。
  • 捕獲/劫持被處理組件的生命週期,常見場景:組件渲染性能追蹤、日誌打點。
  • 屬性代理,能夠給一些子組件傳遞層次比較遠的屬性,並按需求操做他們

HOC的缺陷:

  • 擴展性限制HOC沒法從外部訪問子組件(被包裹組件 WrappedComponent)的 state,所以沒法經過shouldComponentUpdate過濾掉沒必要要的更新(React支持ES6以後,提供了 React.pureComponent來解決這個問題)
  • Ref傳遞問題Ref因爲組件被高階組件包裹,致使被隔斷,須要後來的 React.forwardRef來解決這個問題
  • 層級嵌套HOC可能出現多層包裹組件的狀況(通常不超過兩層,不然很差維護)多層抽象增長了複雜度和理解成本
  • 命名衝突:若是高階組件屢次嵌套,沒有使用 命名空間的話會產生衝突,覆蓋老屬性

Render Props優勢:

  • 上述HOC的缺點,Render Props均可以解決

Render Props缺陷:

  • 使用繁瑣HOC只須要藉助裝飾器/高階函數的特色就能夠進行復用,而 Render Props須要藉助 回調嵌套
  • 嵌套過深Render Props雖然擺脫了組件多層嵌套的問題,可是轉化爲了 函數回調的嵌套

React Hooks的優勢(重點):

  • 簡潔React Hooks解決了 HOCRender Props的嵌套問題,更加簡潔
  • 解耦React Hooks能夠更方便地把 UI 和狀態分離,作到更完全的解耦
  • 無影響複用組件邏輯Hook 使你在無需修改組件結構的狀況下複用狀態邏輯
  • 函數友好:React Hooks爲函數組件而生,從而解決了類組件的幾大問題
    • this 指向容易錯誤
    • 分割在不一樣聲明週期中的邏輯使得代碼難以理解和維護
    • 代碼複用成本高(高階組件容易使代碼量劇增)

React Hooks的缺陷(重點):

  • 額外的學習成本(Functional Component 與 Class Component 之間的困惑)
  • 寫法上有限制(不能出如今條件、循環中,只能在最外層調用 Hooks),而且寫法限制增長了重構成本
  • 破壞了 PureComponent、React.memo淺比較的性能優化效果(爲了取最新的props和state,每次render()都要從新建立事件處函數)(依賴項不變,可解決該問題)
  • 使用不當,可能會形成 閉包陷阱問題
  • React.memo並不能徹底替代 shouldComponentUpdate(由於拿不到 state change,只針對 props change)

關於react-hooks的評價來源於官方react-hooks RFC

感謝你們

若是你以爲這篇內容對你挺有啓發,我想邀請你幫我三個小忙:

  1. 點個「 在看」,讓更多的人也能看到這篇內容(喜歡不點在看,都是耍流氓 -_-)
  2. 歡迎加我微信「 Augenstern9728」一塊兒交流學習...
  3. 關注公衆號「 前端時光屋」,持續爲你推送精選好文。

本文分享自微信公衆號 - 前端時光屋(javascriptlab)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索