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 - 前端组件化(三):抽象出公共组件类》咱们把这个通用模式抽离到一个类当中。

相关文章
相关标签/搜索