從 0 到 1 實現 React 系列 —— 5.PureComponent 實現 && HOC 探幽

來源:互聯網
上載者:User

本系列文章在實現一個 cpreact 的同時協助大家理順 React 架構的核心內容(JSX/虛擬DOM/組件/生命週期/diff演算法/setState/PureComponent/HOC/...) 項目地址

  • 從 0 到 1 實現 React 系列 —— JSX 和 Virtual DOM
  • 從 0 到 1 實現 React 系列 —— 組件和 state|props
  • 從 0 到 1 實現 React 系列 —— 生命週期和 diff 演算法
  • 從 0 到 1 實現 React 系列 —— 最佳化 setState 和 ref 的實現
  • 從 0 到 1 實現 React 系列 —— PureComponent 實現 && HOC 探幽
PureComponent 精髓

使用 PureComponent 是最佳化 React 效能的一種常用手段,相較於 Component, PureComponent 會在 render 之前自動執行一次 shouldComponentUpdate() 函數,根據返回的 bool 值判斷是否進行 render。其中有個重點是 PureComponent 在 shouldComponentUpdate() 的時候會進行 shallowEqual(淺比較)。

PureComponent 的淺比較策略如下:

對 prevState/nextState 以及 prevProps/nextProps 這兩組資料進行淺比較:

1.對象第一層資料未發生改變,render 方法不會觸發;
2.對象第一層資料發生改變(包括第一層資料引用的改變),render 方法會觸發;

PureComponent 的實現

照著上述思路我們來實現 PureComponent 的邏輯

function PureComponent(props) {  this.props = props || {}  this.state = {}  isShouldComponentUpdate.call(this) // 為每個 PureComponent 綁定 shouldComponentUpdate 方法}PureComponent.prototype.setState = function(updater, cb) {  isShouldComponentUpdate.call(this) // 調用 setState 時,讓 this 指向子類的執行個體,目的取到子類的 this.state  asyncRender(updater, this, cb)}function isShouldComponentUpdate() {  const cpState = this.state  const cpProps = this.props  this.shouldComponentUpdate = function (nextProps, nextState) {    if (!shallowEqual(cpState, nextState) || !shallowEqual(cpProps, nextProps)) {      return true  // 只要 state 或 props 淺比較不等的話,就進行渲染    } else {      return false // 淺比較相等的話,不渲染    }  }}// 淺比較邏輯const shallowEqual = function(oldState, nextState) {  const oldKeys = Object.keys(oldState)  const newKeys = Object.keys(nextState)  if (oldKeys.length !== newKeys.length) {    return false  }  let flag = true  for (let i = 0; i < oldKeys.length; i++) {    if (!nextState.hasOwnProperty(oldKeys[i])) {      flag = false      break    }    if (nextState[oldKeys[i]] !== oldState[oldKeys[i]]) {      flag = false      break    }  }  return flag}
測試案例

測試案例用 在 React 上提的一個 issue 中的案例,我們期望點擊增加按鈕後,頁面上顯示的值能夠加 1。

class B extends PureComponent {  constructor(props) {    super(props)    this.state = {      count: 0    }    this.click = this.click.bind(this)  }  click() {    this.setState({      count: ++this.state.count,    })  }  render() {    return (      <div>        <button onClick={this.click}>增加</button>        <div>{this.state.count}</div>      </div>    )  }}

然而,我們點擊上述代碼,頁面上顯示的 0 分毫不動!!!

揭秘如下:

click() {  const t = ++this.state.count  console.log(t === this.state.count) // true  this.setState({    count: t,  })}

當點擊增加按鈕,控制台顯示 t === this.state.count 為 true, 也就說明了 setState 前後的狀態是統一的,所以 shallowEqual(淺比較) 返回的是 true,致使 shouldComponentUpdate 返回了 false,頁面因此沒有渲染。

類似的,如下寫法也是達不到目標的,留給讀者思考了。

click() {  this.setState({    count: this.state.count++,  })}

那麼如何達到我們期望的目標呢。揭秘如下:

click() {  this.setState({    count: this.state.count + 1  })}

感悟:小小的一行代碼裡蘊藏著無數的 bug。

HOC 實踐

高階組件(Higher Order Component) 不屬於 React API 範疇,但是它在 React 中也是一種實用的技術,它可以將常見任務抽象成一個可重用的部分。這個小節算是番外篇,會結合 cpreact(前文實現的類 react 輪子) 與 HOC 進行相關的實踐。

它可以用如下公式表示:

y = f(x),// x:原有組件// y:高階組件// f():

f() 的實現有兩種方法,下面進行實踐。

屬性代理(Props Proxy)

這類實現也是裝飾器模式的一種運用,通過裝飾器函數給原來函數賦能。下面例子在裝飾器函數中給被裝飾的組件傳遞了額外的屬性 { a: 1, b: 2 }。

聲明:下文所展示的 demo 均已在 cpreact 測試通過

function ppHOC(WrappedComponent) {  return class extends Component {    render() {      const obj = { a: 1, b: 2 }      return (        <WrappedComponent { ...this.props } { ...obj } />      )    }  }}@ppHOCclass B extends Component {  render() {    return (      <div>        { this.props.a + this.props.b } { /* 輸出 3 */ }      </div>    )  }}

要是將 { a: 1, b: 2 } 替換成全域共用對象,那麼不就是 react-redux 中的 Connect 了麼?

改進上述 demo,我們就可以實現可插拔的受控組件,代碼示意如下:

function ppDecorate(WrappedComponent) {  return class extends Component {    constructor() {      super()      this.state = {        value: ''      }      this.onChange = this.onChange.bind(this)    }    onChange(e) {      this.setState({        value: e.target.value      })    }    render() {      const obj = {        onChange: this.onChange,        value: this.state.value,      }      return (        <WrappedComponent { ...this.props } { ...obj } />      )    }  }}@ppDecorateclass B extends Component {  render() {    return (      <div>        <input { ...this.props } />        <div>{ this.props.value }</div>      </div>    )  }}

效果如:

這裡有個坑點,當我們在輸入框輸入字元的時候,並不會立馬觸發 onChange 事件(我們想要讓事件立即觸發,然而現在要按下斷行符號鍵或者點下滑鼠才觸發),在 react 中有個合成事件 的知識點,下篇文章會進行探究。

順帶一提在這個 demo 中似乎看到了雙向繫結的效果,但是實際中 React 並沒有雙向繫結的概念,但是我們可以運用 HOC 的知識點結合 setState 在 React 表單中實現偽雙向繫結的效果。

繼承反轉(Inheritance Inversion)

繼承反轉的核心是:傳入 HOC 的組件會作為返回類的父類來使用。然後在 render 中調用 super.render() 來調用父類的 render 方法。

在 《ES6 繼承與 ES5 繼承的差異》中我們提到了作為對象使用的 super 指向父類的執行個體。

function iiHOC(WrappedComponent) {  return class extends WrappedComponent {    render() {      const parentRender = super.render()      if (parentRender.nodeName === 'span') {        return (          <span>繼承反轉</span>        )      }    }  }}@iiHOCclass B extends Component {  render() {    return (      <span>Inheritance Inversion</span>    )  }}

在這個 demo 中,在 HOC 內實現了渲染劫持,頁面上最終顯示如下:

可能會有疑惑,使用屬性代理的方式貌似也能實現渲染劫持呀,但是那樣做沒有繼承反轉這種方式純粹。

鳴謝

Especially thank simple-react for the guidance function of this library. At the meantime,respect for preact and react

相關連結
  • A doubt behaviour using the PureComponent
  • React 的效能最佳化(一)當 PureComponent 遇上 ImmutableJS
  • React效能最佳化方案之PureComponent
  • 帶著三個問題深入淺出React高階組件
  • 深入理解 React 高階組件
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.