看看上一節咱們的代碼,仔細留意一下 changeLikeText
函數,這個函數包含了 DOM 操做,如今看起來比較簡單,那是由於如今只有 isLiked
一個狀態。因爲數據狀態改變會致使須要咱們去更新頁面的內容,因此假想一下,若是你的組件依賴了不少狀態,那麼你的組件基本所有都是 DOM 操做。html
一個組件的顯示形態由多個狀態決定的狀況很是常見。代碼中混雜着對 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 } }
其實只是改了幾個小地方:瀏覽器
render
函數裏面的 HTML 字符串會根據 this.state
不一樣而不一樣(這裏是用了 ES6 的模版字符串,作這種事情很方便)。setState
函數,這個函數接受一個對象做爲參數;它會設置實例的 state
,而後從新調用一下 render
方法。changeLikeText
會構建新的 state
對象,這個新的 state
,傳入 setState
函數當中。這樣的結果就是,用戶每次點擊,changeLikeText
都會調用改變組件狀態而後調用 setState
;setState
會調用 render
,render
方法會根據 state
的不一樣從新構建不一樣的 DOM 元素。app
也就是說,你只要調用 setState
,組件就會從新渲染。咱們順利地消除了手動的 DOM 操做。less
上面的改進不會有什麼效果,由於你仔細看一下就會發現,其實從新渲染的 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
方法要從新寫一遍,其實這些東西均可以抽出來,變成一個通用的模式。下一節咱們把這個通用模式抽離到一個類當中。