【Ts重构Vue】00-Ts重构Vue前言

为何重构

本科机械设计制造及其自动化,16年稀里糊涂的进了一家干变厂,17年自学了大半年,18年正式跨行来到前端。工做中主要写业务代码,不多涉及造轮子工做,一直但愿可以提升编程能力。刚好,公司业务栈以vue为主,理解它的逻辑,相信对从此确定会有帮助。因而就有了使用ts重构vue的冲动。更甚者,但愿可以参与到开源社区的建设,努力变得更好。html

Vue的功能仍是很复杂的,源码也涉及到跨平台部分,本次仅学习web方向的源码,指望经过重构引导阅读,增强体会。前端

重构计划

使用到的技术栈以下:vue

  1. TypeScript
  2. Jest
  3. es6
  4. rollup

使用TypeScript编写,使用Jest作单元测试,使用rollup进行构建。这次重构并非彻底的照(拷)搬(贝),将选取经常使用功能去实现。指望最佳的开发模式是:以问题(feature)引领,去阅读源码,理解后经过本身的方式去实现。以虚拟DOM为例,重构过程可能至少分3步实现,一、建立虚拟DOM,二、虚拟DOM映射为真实DOM,三、给真实DOM设置其余属性(style、event等)。node

Vue分为运行版和完整版,完整版本包含compile模块,对如<div id="app">{{message}}</div>的模版语法进行了编译。为了简化理解逻辑,笔者重构时直接将compile模块忽略了,所有经过渲染函数render进行编写。git

必备基础知识

  1. 虚拟DOM

谈到三大框架,一定要了解虚拟DOM。经过访问vm._vnode能够查看vue虚拟DOM的结构,借助children属性实现了DOM的树形结构。es6

虚拟DOM是什么? 怎么定义虚拟DOM? 虚拟DOM有什么好处?github

推荐阅读snabbdom开源库,snabbdom的核心很是精简,总共318行,最关键一点,Vue的虚拟DOM是参考它改进的。web

  1. Proxy和Reflect使用

谈到Vue的特性,一定要了解数据驱动和响应式。经过访问app._data能够查看处理后的data结构。express

Vue源码使用Object.defineProperty,笔者将使用Proxy进行属性拦截,以下述代码,实例化后经过p.name能够访问到this.data={name: 'xiaoming'}的属性。编程

class P {
  constructor () {
    this.data = {name: 'xiaoming'}
    return new Proxy(this, {
      get (target, key) {
        return Reflect.get(target.data, key)
      }
    })
  }
}
let p = new P()
console.log(p.name)
复制代码
  1. 事件循环(nextTick)

观察下面demo,在定时器中修改namemessage属性,能够发现视图更新了。此时,runCount的值是多少?若是咱们同步修改了更多的属性,会影响runCount的值吗?

let runCount = 0
let vm = new Vue({
  el: '#app',
  data: {
    name: 'xiaoming',
    message: 'Hello Vue!'
  },
  render (h) {
    runCount += 1
    return h('h1', this.name + this.message)
  }
})
setTimeout(() => {
  vm.name = 'xiaohong'
  vm.message = 'Hello world'
}, 1000)

复制代码

Vue源码对渲染过程进行了优化,其每次更新都是异步的。此外,你是否在业务中使用到$nextTick,为何会用到它?了解Js的事件循环,有助于理解Vue的更新原理。

定下小目标

  1. 基本使用

观察下面demo,用户首先看到Hello Vue!,1秒钟以后观看到UI变化Hello world

vue是如何进行渲染的?当修改message值,又是如何进行更新的?

let vm = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  },
  render (h) {
    return h('h1', this.message)
  }
})

setTimeout(() => {
  vm.message = 'Hello world'
}, 1000)
复制代码
  1. 事件绑定

观察下面demo,当用户点击按钮时,点击次数加1,同时控制台输出customClick click

Vue是如何绑定事件的?自定义事件和原生事件的处理方式有何不一样?

有时候在项目中可能会使用到eventBus,这又是如何实现的?

Vue.component('button-count', {
  data () {
    return {
      count: 0
    }
  },
  render (h) {
    const self = this
    return h('button', {
      on: {
        click () {
          self.count += 1
          self.$emit('customClick', self.count)
        }
      }
    }, `点击次数:${this.count}`)
  }
})
let vm = new Vue({
  el: '#app',
  render (h) {
    return h('button-count', {
      nativeOn: {
        click () {
          console.log('click')
        }
      },
      on: {
        customClick () {
          console.log('customClick')
        }
      }
    })
  }
})
复制代码
  1. 组件

咱们定义了button-count组件,当用户点击时,组件自动记录点击次数并更新视图。假设用户点击了2次,此时的runButtonCountrunCount的值分别是多少?为何是这样的?

Vue不只支持自定义组件,也内置了transition/keep-alive等组件,Vue是如何实现组件功能的?父子组件如何进行消息传递?

let runButtonCount = 0
let runCount = 0
Vue.component('button-count', {
  data () {
    return {
      count: 0
    }
  },
  render (h) {
    runButtonCount += 1
    const self = this
    return h('button', {
      on: {
        click () {
          self.count += 1
        }
      }
    }, `点击次数:${this.count}`)
  }
})
let vm = new Vue({
  el: '#app',
  render (h) {
    runCount += 1
    return h('button-count')
  }
})
复制代码
  1. 指令

观察下面demo,用户首先看不到任何文字,1秒钟以后观看到111&222&333

Vue不只支持内置指令,也容许用户自定义指令。指令代码是如何控制UI的?

ps:新的项目使用vue进行开发,以iframe形式被嵌入在老页面中。iframe技术仍是很是有效,但咱们发现新项目中的弹窗没法全屏展现(半透明mask没法全屏),后来借助指令解决了问题。推荐开源库https://github.com/calebroseland/vue-dom-portal。

let vm = new Vue({
  el: '#app',
  data () {
    return  {
        news: [111, 222]
    }
  },
  render (h) {
    const self = this
    return h('h1', {
      directives: [
        {
          name: 'show',
          value: self.news.length > 2,
          expression: 'news.length > 2', 
          arg: '',
          modifiers: { }
        }
      ]
    }, self.news.join('&'))
  }
})

setTimeout(() => {
  vm.news.push(333)
}, 1000)
复制代码
  1. 插槽

观察下面demo,页面最终输出什么内容?插槽功能是如何实现的?

Vue.component('app-layout', {
  render (h) {
    const self = this
    return h('div', [
      h('header', [self._t('header')]),
      h('main', [self._t('default', [h('', '默认内容')])]),
      h('footer', [self._t('footer')])
    ])
  }
})

let v = new Vue({
  el: '#app',
  data () {
    return  {
      title: 'hello world!',
      msg: 'msg',
      desc: 'desc'
    }
  },
  render (h) {
    return h('div', [
      h('app-layout', [
        h('h1', {attrs: {slot: 'header'}, slot: 'header'}, this.title),
        h('p', this.msg),
        h('p', {attrs: {slot: 'footer'}, slot: 'footer'}, this.desc)
      ])
    ])
  }
})
复制代码

页面渲染后,最终的demo结构以下:

<div>
  <header>
    <h1>hello world!</h1>
  </header>
  <main>
    <p>msg</p>
  </main>
  <footer>
    <p>desc</p>
  </footer>
</div>
复制代码
  1. 路由(拓展)

待完善

总结

前面咱们定下了不少小目标,接下来就同样样去实现。咱们的目标很简单,就是demo可以按照要求运行。

vue源码很复杂,有跨平台代码(web、weex、server),有性能监控代码。看源码时切记不要完美主义,不必必须理解全部的代码。经过问题主线去阅读,去了解vue实现的原理。

杠精一下

为何使用ts?

为何使用rollup?

推荐阅读

笔者重构Vue的储备均来黄老师的两套课程,再次着重推荐。文章有些demo来自黄老师,不知道是否侵权(若有侵权一定删除)。

ts学习推荐:coding.imooc.com/class/chapt…

vue源码学习:ustbhuangyi.github.io/vue-analysi…

系列文章

【Ts重构Vue】00-Ts重构Vue前言

【Ts重构Vue】01-如何建立虚拟节点

【Ts重构Vue】02-数据如何驱动视图变化

【Ts重构Vue】03-如何给真实DOM设置样式

【Ts重构Vue】04-异步渲染

【Ts重构Vue】05-实现computed和watch功能

相关文章
相关标签/搜索