前端為什麼要使用元件化的思想,通過一個例項來分析
在平時專案中,為什麼我們都會採用元件化的思想去編寫程式碼?
其實的原因很簡單!!! 我們在寫程式碼的時候,寫完以後發現這個程式碼會出現在其他地方,想要複用,或者同事感興趣,想使用這個程式碼。這個時候我們就需要通過元件化來實現程式碼的複用了,否則工作量真的是………..
接下來通過一個例子大概分析一下:
這個是一個點讚的功能~ 如果給我寫,那 簡單 啊
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
}
}
其實只是改了幾個小地方:
render 函式裡面的 HTML 字串會根據 this.state 不同而不同(這裡是用了 ES6
的模版字串,做這種事情很方便)。新增一個 setState 函式,這個函式接受一個物件作為引數;它會設定例項的
當用戶點選按鈕的時候, 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) // 刪除舊的元素
}