1. 程式人生 > >前端為什麼要使用元件化的思想,通過一個例項來分析

前端為什麼要使用元件化的思想,通過一個例項來分析

在平時專案中,為什麼我們都會採用元件化的思想去編寫程式碼?

其實的原因很簡單!!! 我們在寫程式碼的時候,寫完以後發現這個程式碼會出現在其他地方,想要複用,或者同事感興趣,想使用這個程式碼。這個時候我們就需要通過元件化來實現程式碼的複用了,否則工作量真的是………..

接下來通過一個例子大概分析一下:

這裡寫圖片描述
這個是一個點讚的功能~ 如果給我寫,那 簡單

HTML

 <body>
    <div class='wrapper'>
      <button class='like-btn'>
        <span
class='like-text'>
點贊</span> <span>(假裝有一張手的小圖示)</span> </button> </div> </body>

JavaScript

  const button = document.querySelector('.like-btn')
  const buttonText = button.querySelector('.like-text')
  let isLiked = false
  button.addEventListener('click'
, () => { isLiked = !isLiked if (isLiked) { buttonText.innerHTML = '取消' } else { buttonText.innerHTML = '點贊' } }, false)

程式碼寫完以後,這時候你的同事跑過來了,說他很喜歡你的按鈕,他也想用你寫的這個點贊功能。這時候問題就來了,你就會發現這種實現方式很致命:你的同事要把整個 button 和裡面的結構複製過去,還有整段 JavaScript 程式碼也要複製過去。這樣的實現方式沒有任何可複用性。

實現簡單的元件化

這裡有一個將字串,變成DOM的函式

// ::String => ::Document
const createDOMFromString = (domString) => {
  const div = document.createElement('div')
  div.innerHTML = domString
  return div
}

按元件處理以後

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

    changeLikeText () {
      const likeText = this.el.querySelector('.like-text')
      this.state.isLiked = !this.state.isLiked
      likeText.innerHTML = this.state.isLiked ? '取消' : '點贊'
    }

    render () {
      this.el = createDOMFromString(`
        <button class='like-button'>
          <span class='like-text'>點贊</span>
          <span>(假裝有一張手的小圖示)</span>
        </button>
      `)
      this.el.addEventListener('click', this.changeLikeText.bind(this), false)
      return this.el
    }
  }

在別的地方使用元件

現在這個元件的可複用性已經很不錯了,你的同事們只要例項化一下然後插入到 DOM 裡面去就好了。

  const wrapper = document.querySelector('.wrapper')

  const likeButton1 = new LikeButton()
  wrapper.appendChild(likeButton1.render())

  const likeButton2 = new LikeButton()
  wrapper.appendChild(likeButton2.render())

一個元件的顯示多個狀態

一個元件的顯示形態由多個狀態決定的情況非常常見。程式碼中混雜著對 DOM 的操作其實是一種不好的實踐,手動管理資料和 DOM 之間的關係會導致程式碼可維護性變差、容易出錯。

這裡要提出的一種解決方案:

一旦狀態發生改變,就重新呼叫 render 方法,構建一個新的 DOM 元素。這樣做的好處是什麼呢?好處就是你可以在 render 方法裡面使用最新的 this.state 來構造不同 HTML 結構的字串,並且通過這個字串構造不同的 DOM 元素。頁面就更新了!聽起來有點繞,看看程式碼怎麼寫,修改原來的程式碼為:

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

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

    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 函式,這個函式接受一個物件作為引數;它會設定例項的

  3. 當用戶點選按鈕的時候, changeLikeText 會構建新的 state
    物件,這個新的 state ,傳入 setState 函式當中。

這樣的結果就是,使用者每次點選,changeLikeText 都會呼叫改變元件狀態然後呼叫 setState ;setState 會呼叫 render ,render 方法會根據 state 的不同重新構建不同的 DOM 元素。

也就是說,你只要呼叫 setState,元件就會重新渲染。我們順利地消除了手動的 DOM 操作。

在別的地方使用元件


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