# vue源码 挂载和渲染
template
compiled into => render function
retures => virtual dom
generated => actual dom
# base
# runtime only
- 自己创建vue实例,手写render函数也可以执行
new vue({
render(h){
return h('div',this.hi)
}
})
- 要借助
vue-loader
进行编译,将编译交给插件去实现
# runtime + compiler
- 可以用个模版,通过挂载,是包含编译器的版本
new vue ({
template:'{{yiki}}'
})
# virtual dom
- 虚拟dom 就是将页面抽象为js对象,本质作为js和真实dom的中间层
- diff
[object htmldiveele] - brower native obj { tag : 'div', data: { attrs: {} , ...}, children: []} ^ plain js obj
# vnode
- /core/vnode.js
constructor (
tag?: string,
data?: VNodeData,
children?: ?Array<VNode>,
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions,
asyncFactory?: Function
) {
...
}
createEmptyVNode
创建空vnodecreateTextVNode
- /src/core/vdom包下有各种相关
# 模版到真实节点的流程
- 解析:html模版解析为ast树
- ast树生成可执行的render函数
- 生成渲染函数render函数,转换为vnode对象
- 生成真的节点
# 挂载
- 什么是挂载
- 传递template为属性的模版字符串,通过中间过程转为真实的dom节点,并挂载到el代表的根结点上完成渲染
- /core/instance/init.js
... // 一系列别的初始化,开始mount挂载
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
...
- /src/platforms/rumtime/index.js
// public mount method 真正的挂载方法
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
mountComponent
/src/core/instance/lifecycle.js- 注意这里挂载前会创建一个
watcher
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
...
updateComponent = () => {
// render函数渲染出虚拟dom,虚拟dom渲染成真实dom
vm._update(vm._render(), hydrating)
}
...
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
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
- /src/platforms/entry-runtime-with-compiler.js
// 重新定义$mount
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
process.env.NODE_ENV !== 'production' && warn(
`Do not mount Vue to <html> or <body> - mount to normal elements instead.`
)
return this
}
const options = this.$options
// resolve template/el and convert to render function
if (!options.render) {
let template = options.template
if (template) {
if (typeof template === 'string') {
// 内部编译器编译模版
}
} 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)
}
...
}
// 最终调用
return mount.call(this, el, hydrating)
}
...
template
会转为语法树astcompileToFunctions
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,// 是否改变纯文本插入分隔符
comments: options.comments // html注释
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
# vm._render
- 回到
mountComponent
/src/core/instance/lifecycle.js
updateComponent = () => {
// render函数渲染出虚拟dom,虚拟dom渲染成真实dom
vm._update(vm._render(), hydrating)
}
vm._render
是执行render
渲染,生成虚拟dom_render
/core/instance/render.js
export function renderMixin (Vue: Class<Component>) {
...
Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
...
let vnode
try {
currentRenderingInstance = vm
// 数据代理过滤 + call(obj,this) 的this
vnode = render.call(vm._renderProxy, vm.$createElement)
// 用于 render:()=>{return createelement('div',this.message)}
} catch (e) {
...
} finally {
currentRenderingInstance = null
}
...
return vnode
}
}
$createElement
in render.js
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
# vm.update
vm._update
转为真是dom节点_update
in lifecycleMixin
export function lifecycleMixin (Vue: Class<Component>) {
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
const restoreActiveInstance = setActiveInstance(vm)
vm._vnode = vnode
// based on the rendering backend used.
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
}
vm.__patch__
是浏览器环境下pathc
函数的引用vm.__patch__
内部会通过createElm
去创建真实的DOM元素,期间遇到子Vnode
会递归调用createElm
方法。vnode
是一个不断创建子节点的过程