前端組件化(二):優化 DOM 操做

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

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

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

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

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
    }
  }

其實只是改了幾個小地方:瀏覽器

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

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

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

從新插入新的 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 元素
likeButton.onStateChange = (oldEl, newEl) => {
  wrapper.insertBefore(newEl, oldEl) // 插入新的元素
  wrapper.removeChild(oldEl) // 刪除舊的元素
}

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

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

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

相關文章
相關標籤/搜索