React.js 小書 Lesson3 - 前端組件化(二):優化 DOM 操做


React.js 小書 Lesson3 - 前端組件化(二):優化 DOM 操做

本文做者:鬍子大哈
本文原文:huziketang.com/books/react…javascript

轉載請註明出處,保留原文連接以及做者信息前端

在線閱讀:huziketang.com/books/react…java


看看上一節前端組件化(一):從一個簡單的例子講起咱們的代碼,仔細留意一下 changeLikeText 函數,這個函數包含了 DOM 操做,如今看起來比較簡單,那是由於如今只有 isLiked 一個狀態。因爲數據狀態改變會致使須要咱們去更新頁面的內容,因此假想一下,若是你的組件依賴了不少狀態,那麼你的組件基本所有都是 DOM 操做。react

一個組件的顯示形態由多個狀態決定的狀況很是常見。代碼中混雜着對 DOM 的操做實際上是一種很差的實踐,手動管理數據和 DOM 之間的關係會致使代碼可維護性變差、容易出錯。因此咱們的例子這裏還有優化的空間:如何儘可能減小這種手動 DOM 操做?瀏覽器

狀態改變 -> 構建新的 DOM 元素更新頁面

這裏要提出的一種解決方案:一旦狀態發生改變,就從新調用 render 方法,構建一個新的 DOM 元素。這樣作的好處是什麼呢?好處就是你能夠在 render 方法裏面使用最新的 this.state 來構造不一樣 HTML 結構的字符串,而且經過這個字符串構造不一樣的 DOM 元素。頁面就更新了!聽起來有點繞,看看代碼怎麼寫,修改原來的代碼爲:app

class LikeButton {
    constructor () {
      this.state = { isLiked: false }
    }

    setState (state) {
      this.state = state
      this.el = this.render()
    }

    changeLikeText () {
      this.setState({
        isLiked: !this.state.isLiked
      })
    }

    render () {
      this.el = createDOMFromString(` <button class='like-btn'> <span class='like-text'>${this.state.isLiked ? '取消' : '點贊'}</span> <span>👍</span> </button> `)
      this.el.addEventListener('click', this.changeLikeText.bind(this), false)
      return this.el
    }
  }複製代碼

其實只是改了幾個小地方:less

  1. render 函數裏面的 HTML 字符串會根據 this.state 不一樣而不一樣(這裏是用了 ES6 的模版字符串,作這種事情很方便)。
  2. 新增一個 setState 函數,這個函數接受一個對象做爲參數;它會設置實例的 state,而後從新調用一下 render 方法。
  3. 當用戶點擊按鈕的時候, changeLikeText 會構建新的 state 對象,這個新的 state ,傳入 setState 函數當中。

這樣的結果就是,用戶每次點擊,changeLikeText 都會調用改變組件狀態而後調用 setStatesetState 會調用 renderrender 方法會根據 state 的不一樣從新構建不一樣的 DOM 元素。函數

也就是說,你只要調用 setState,組件就會從新渲染。咱們順利地消除了手動的 DOM 操做。組件化

從新插入新的 DOM 元素

上面的改進不會有什麼效果,由於你仔細看一下就會發現,其實從新渲染的 DOM 元素並無插入到頁面當中。因此在這個組件外面,你須要知道這個組件發生了改變,而且把新的 DOM 元素更新到頁面當中。性能

從新修改一下 setState 方法:

...
    setState (state) {
      const oldEl = this.el
      this.state = state
      this.el = this.render()
      if (this.onStateChange) this.onStateChange(oldEl, this.el)
    }
...複製代碼

使用這個組件的時候:

const likeButton = new LikeButton()
wrapper.appendChild(likeButton.render()) // 第一次插入 DOM 元素
component.onStateChange = (oldEl, newEl) => {
  wrapper.insertBefore(newEl, oldEl) // 插入新的元素
  wrapper.removeChild(oldEl) // 刪除舊的元素
}複製代碼

這裏每次 setState 都會調用 onStateChange 方法,而這個方法是實例化之後時候被設置的,因此你能夠自定義 onStateChange 的行爲。這裏作的事是,每當 setState 中構造完新的 DOM 元素之後,就會經過 onStateChange 告知外部插入新的 DOM 元素,而後刪除舊的元素,頁面就更新了。這裏已經作到了進一步的優化了:如今不須要再手動更新頁面了。

非通常的暴力,由於每次 setState 都從新構造、新增、刪除 DOM 元素,會致使瀏覽器進行大量的重排,嚴重影響性能。不過沒有關係,這種暴力行爲能夠被一種叫 Virtual-DOM 的策略規避掉,但這不是本文所討論的範圍。

這個版本的點贊功能很不錯,我能夠繼續往上面加功能,並且還不須要手動操做DOM。可是有一個很差的地方,若是我要從新另外作一個新組件,譬如說評論組件,那麼裏面的這些 setState 方法要從新寫一遍,其實這些東西均可以抽出來,變成一個通用的模式。

下一節《React.js 小書 Lesson4 - 前端組件化(三):抽象出公共組件類》咱們把這個通用模式抽離到一個類當中。

相關文章
相關標籤/搜索