投身烈火
Keep Walking

vue源码阅读笔记(9)

上次看了事件部分的代码,这次准备看 render 的代码

Vue构造函数和vue实例 instance

render.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/* @flow */
import {
warn,
nextTick,
toNumber,
_toString,
looseEqual,
emptyObject,
handleError,
looseIndexOf
} from '../util/index'
import VNode, {
cloneVNodes,
createTextVNode,
createEmptyVNode
} from '../vdom/vnode'
import { createElement } from '../vdom/create-element'
import { renderList } from './render-helpers/render-list'
import { renderSlot } from './render-helpers/render-slot'
import { resolveFilter } from './render-helpers/resolve-filter'
import { checkKeyCodes } from './render-helpers/check-keycodes'
import { bindObjectProps } from './render-helpers/bind-object-props'
import { renderStatic, markOnce } from './render-helpers/render-static'
import { resolveSlots, resolveScopedSlots } from './render-helpers/resolve-slots'
export function initRender (vm: Component) {
// $vnode对应的是虚拟组件的vnode
vm.$vnode = null // the placeholder node in parent tree
// _vnode对应的是真是dom节点的vnode
vm._vnode = null // the root of the child tree
// 用来存储渲染静态子树函数的列表
vm._staticTrees = null
const parentVnode = vm.$options._parentVnode
const renderContext = parentVnode && parentVnode.context
// resolveSlots可以用来收集所有的slot标签,_renderChildren是子节点列表
vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext)
// 2.1新增特性,可以给slot指定执行的作用域
vm.$scopedSlots = emptyObject
// _c和$createElement都是对createElement的封装,相当于bind
// 不同的是_c是内部使用的,createElement对应了render函数里的createElement方法
// 所以createElement使用的是标准模式,_c使用的是简略模式
// bind the createElement fn to this instance
// so that we get proper render context inside it.
// args order: tag, data, children, normalizationType, alwaysNormalize
// internal version is used by render functions compiled from templates
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
}
export function renderMixin (Vue: Class<Component>) {
//
Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this)
}
// 用来生成vnode的函数
Vue.prototype._render = function (): VNode {
const vm: Component = this
const {
render,
// 这个staticRenderFns是在解析模板时自动生成的
staticRenderFns,
_parentVnode
} = vm.$options
// 如果节点已经安装过,则直接把slot复制出来,应该也是为了效率提升吧
if (vm._isMounted) {
// clone slot nodes on re-renders
for (const key in vm.$slots) {
vm.$slots[key] = cloneVNodes(vm.$slots[key])
}
}
// 生成scopedSlots
vm.$scopedSlots = (_parentVnode && _parentVnode.data.scopedSlots) || emptyObject
if (staticRenderFns && !vm._staticTrees) {
vm._staticTrees = []
}
// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
vm.$vnode = _parentVnode
// render self
// vnode对应了组件下的真实元素的vnode
let vnode
try {
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
handleError(e, vm, `render function`)
// return error render result,
// or previous vnode to prevent render error causing blank component
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
// renderError是渲染失败时的错误回调
vnode = vm.$options.renderError
? vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
: vm._vnode
} else {
vnode = vm._vnode
}
}
// 如果渲染出错就返回空节点
// return empty vnode in case the render function errored out
if (!(vnode instanceof VNode)) {
if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
warn(
'Multiple root nodes returned from render function. Render function ' +
'should return a single root node.',
vm
)
}
vnode = createEmptyVNode()
}
// 设置当前vnode的parent
// set parent
vnode.parent = _parentVnode
return vnode
}
// 这些方法都是给vue ast解析出来的模板函数用的,对应的转换函数
// internal render helpers.
// these are exposed on the instance prototype to reduce generated render
// code size.
Vue.prototype._o = markOnce
Vue.prototype._n = toNumber
Vue.prototype._s = _toString
Vue.prototype._l = renderList
Vue.prototype._t = renderSlot
Vue.prototype._q = looseEqual
Vue.prototype._i = looseIndexOf
Vue.prototype._m = renderStatic
Vue.prototype._f = resolveFilter
Vue.prototype._k = checkKeyCodes
Vue.prototype._b = bindObjectProps
Vue.prototype._v = createTextVNode
Vue.prototype._e = createEmptyVNode
Vue.prototype._u = resolveScopedSlots
}

看完render大概就能理解之前看lifecycle不理解的地方了。vue里面vm和vnode并不是一一对应的。比如有个自定义的组件,那么my-component对应一个vnode,然后的render里面的第一个子节点(render要求模板必须有一个根节点)有对应一个自己的vnode,而vm描述的是当前组件的状态,所以会绑定在my-component模板里的第一个子节点上。其实也很好理解,生成my-component的vnode是在父组件的render中生成的,当时还没实例化vm呢,所以才只能在渲染子组件的时候再在vm上绑定parentVnode和当前节点的vnode……总之之前弄不清楚_vnode和$vnode的关系,现在算是搞明白了。

好的那么由于时间不足,本期的博客就写到这里了,如果不出意外的话,maybe可能也许大概下周五会更新吧,能不能准时更新,就全看米娜桑点赞转发安利留言的力度了~!

白了个白~!