本科机械设计制造及其自动化,16年稀里糊涂的进了一家干变厂,17年自学了大半年,18年正式跨行来到前端。工做中主要写业务代码,不多涉及造轮子工做,一直但愿可以提升编程能力。刚好,公司业务栈以vue为主,理解它的逻辑,相信对从此确定会有帮助。因而就有了使用ts重构vue的冲动。更甚者,但愿可以参与到开源社区的建设,努力变得更好。html
Vue的功能仍是很复杂的,源码也涉及到跨平台部分,本次仅学习web方向的源码,指望经过重构引导阅读,增强体会。前端
使用到的技术栈以下:vue
使用TypeScript编写,使用Jest作单元测试,使用rollup进行构建。这次重构并非彻底的照(拷)搬(贝),将选取经常使用功能去实现。指望最佳的开发模式是:以问题(feature)引领,去阅读源码,理解后经过本身的方式去实现。以虚拟DOM为例,重构过程可能至少分3步实现,一、建立虚拟DOM,二、虚拟DOM映射为真实DOM,三、给真实DOM设置其余属性(style、event等)。node
Vue分为运行版和完整版,完整版本包含compile模块,对如<div id="app">{{message}}</div>
的模版语法进行了编译。为了简化理解逻辑,笔者重构时直接将compile模块忽略了,所有经过渲染函数render
进行编写。git
谈到三大框架,一定要了解虚拟DOM。经过访问vm._vnode
能够查看vue虚拟DOM的结构,借助children属性实现了DOM的树形结构。es6
虚拟DOM是什么? 怎么定义虚拟DOM? 虚拟DOM有什么好处?github
推荐阅读snabbdom开源库,snabbdom的核心很是精简,总共318行,最关键一点,Vue的虚拟DOM是参考它改进的。web
谈到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)
复制代码
观察下面demo,在定时器中修改name
和message
属性,能够发现视图更新了。此时,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的更新原理。
观察下面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)
复制代码
观察下面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')
}
}
})
}
})
复制代码
咱们定义了button-count
组件,当用户点击时,组件自动记录点击次数并更新视图。假设用户点击了2次,此时的runButtonCount
和runCount
的值分别是多少?为何是这样的?
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')
}
})
复制代码
观察下面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)
复制代码
观察下面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>
复制代码
待完善
前面咱们定下了不少小目标,接下来就同样样去实现。咱们的目标很简单,就是demo可以按照要求运行。
vue源码很复杂,有跨平台代码(web、weex、server),有性能监控代码。看源码时切记不要完美主义,不必必须理解全部的代码。经过问题主线去阅读,去了解vue实现的原理。
为何使用ts?
为何使用rollup?
笔者重构Vue的储备均来黄老师的两套课程,再次着重推荐。文章有些demo来自黄老师,不知道是否侵权(若有侵权一定删除)。
ts学习推荐:coding.imooc.com/class/chapt…
vue源码学习:ustbhuangyi.github.io/vue-analysi…