vue源码 ---- 数据驱动
数据驱动的理解
指视图由数据驱动生成,也就是说我们修改视图时并不会直接操作DOM,而是借助修改数据来达到目的,这样使代码更利于维护。
// 最终它会在页面上渲染出 Hello Vue。接下来,我们会从源码角度来分析 Vue 是如何实现的
<div id="app">
{{ message }}
</div>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
原理总结
数据驱动原理图:
渲染过程:
1. 初始化vue(this._init)【包括合并配置,初始化生命周期,事件中心,data,props等等】
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
....
....
// 合并配置
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
.....
.....
// 初始化生命周期,初始化事件中心,初始化渲染,初始化 data、props、computed、watcher 等等
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
.....
2. 有el属性后调用
mount)【经过可能的转换使之可以使用render方法,接着实例化渲染Watcher,这个方法会先调用vm._render
方法先生成虚拟 Node,最终调用 vm._update
更新 DOM】
- 当有el属性后,调用 vm.$mount 方法挂载 vm,挂载的目标就是把模板渲染成最终的 DOM:
// 当有el属性后,调用 vm.$mount 方法挂载 vm,挂载的目标就是把模板渲染成最终的 DOM
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
- 如果没有render方法就把 el 或者 template 字符串转换成 render 方法
// 如果没有render方法就把 el 或者 template 字符串转换成 render 方法
if (!options.render) {
let template = options.template
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template)
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && !template) {
warn(
`Template element not found or is empty: ${options.template}`,
this
)
}
}
} else if (template.nodeType) {
template = template.innerHTML
} else {
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
template = getOuterHTML(el)
}
if (template) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile')
}
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile end')
measure(`vue ${this._name} compile`, 'compile', 'compile end')
}
}
}
// 调用原型上的mount方法,Vue.prototype.$mount上主要会return mountComponent方法
return mount.call(this, el, hydrating)
}
- 实例化渲染Watcher
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
3. vm._render
方法(render)通过createElement将实例渲染为Vnode(一个虚拟node)【使用normalizeChildren(children)
和 simpleNormalizeChildren(children)
将children这个参数规范化,然后根据 tag创建对应类型的Vnode】
- VNode 是对真实 DOM 的一种抽象描述。
- 它的核心定义无非就几个关键属性,标签名、数据、子节点、键值等,其它属性都是都是用来扩展 VNode 的灵活性以及实现一些特殊 feature 的。
- 由于 VNode 只是用来映射到真实 DOM 的渲染,不需要包含操作 DOM 的方法,因此它是非常轻量和简单的。
- Virtual DOM 除了它的数据结构的定义,映射到真实的 DOM 实际上要经历 VNode 的 create、diff、patch 等过程。
VNode 的 create 是通过之前提到的createElement
方法创建的,该方法是对_createElement的封装,期间重点是对children(Virtual DOM是树状结构,其中每个VNode也有若干给子节点)的规范,目的是将他们都规范成为 VNode 类型。
4. 执行vm._update
方法(patch),将创建的Vnode渲染为真实的DOM【分为首次渲染和再次更新,其中首次渲染核心是 不断递归createElm
方法,经过判断,创建占位符,传入占位符,先子后父的将DOM插入,最终创建了一个完整的 DOM 树】(DOM)
patch
是平台相关的,在 Web 和 Weex 环境,它们把虚拟 DOM 映射到 “平台 DOM” 的方法是不同的,并且对 “DOM” 包括的属性模块创建和更新也不尽相同。因此每个平台都有各自的 nodeOps 和 modules,但是的主要逻辑部分是相同的
-patch
主要调用了createElm
,createElm 的作用是通过虚拟节点创建真实的 DOM 并插入到它的父节点中。createComponent 方法目的是尝试创建子组件。