1.模板编译
1.1定义
主要将模板template 转化为 渲染函数 render。
- 由于vue需要使用vnode做比较更新,所以需要用到render来返回vnode
- 为了方便用户生成render函数,才使用了template。
- 编译器作用就是把定义好的template自动转化为render。
Vue.js 提供了Compiler + Runtime 和 Runtime only 两个模式
- Compiler + Runtime 是包含编译代码的,可以把编译过程放在运行时做,
- Runtime only后者是不包含编译代码的,需要借助 webpack 的 vue-loader事先把模板一次性编译成 render函数使用,当然我们也可以直接用render函数开发。
1.2入口
//platforms\web\entry-runtime-with-compiler.js
import { compileToFunctions } from './compiler/index'
render转化后
//src\core\instance\render-helpers\index.js
(function anonymous() {
with(this){return _c('div',{attrs:{"id":"demo"}},[
*c('h1',[* v("xxxxx")]),
*v(" "),* c('p',[*v(* s(foo))]),
*v(" "),* c('comp')],1)}
})
- 元素节点使⽤createElement创建,别名_c
- 本⽂节点使⽤createTextVNode创建,别名_v
- 表达式先使⽤toString格式化,别名_s
1.3三个步骤
1.3.1 解析
解析器将模板解析为抽象语法树,基于AST可以做优化或者代码⽣成⼯作。
src/compiler/parser/index.js,
解析器内部分了HTML解析器、⽂本解析器和过滤器解析器,最主要是HTML解析器
1.3.2 优化
优化器的作⽤是在AST中找出静态⼦树并打上标记。静态⼦树是在AST中永远不变的节点,如纯⽂本节点。
src/compiler/optimizer.js
标记静态⼦树的好处:
- 每次重新渲染,不需要为静态⼦树创建新节点
- 虚拟DOM中patch时,可以跳过静态⼦树
1.3.3 代码⽣成
将AST转换成渲染函数中的内容,即代码字符串。
generate⽅法⽣成渲染函数代码
//src/compiler/codegen/index.js
2.组件化机制
2.1组件声明
组件注册使⽤extend⽅法将配置转换为构造函数并添加到components选项
//src/shared/constants.js
export const SSR_ATTR = 'data-server-rendered'
export const ASSET_TYPES = [
'component',
'directive',
'filter'
]
export const LIFECYCLE_HOOKS = [
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroy',
'destroyed',
'activated',
'deactivated',
'errorCaptured',
'serverPrefetch'
]
//src/core/global-api/assets.js
import { ASSET_TYPES } from 'shared/constants'
import { isPlainObject, validateComponentName } from '../util/index'
export function initAssetRegisters (Vue: GlobalAPI) {
/**
* Create asset registration methods.
*/
//注册三个重要的方法 components directives filters
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
if (!definition) {
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
//这里通过extend 生成对应的构造函数
definition = this.options._base.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
//最终输出
//components:{
// 'my-component': {
// template: '<div>children component!</div>'
// }
// }
//这里的 definition 是一个构造函数
this.options[type + 's'][id] = definition
return definition
}
}
})
}
//src/core/global-api/index.js
//开始注册相关方法
initAssetRegisters(Vue)
2.2 组件实例创建及挂载
-
观察⽣成的渲染函数
对应的render返回的渲染函数是_c("comp") ,//这里comp是自定义标签做特殊处理
2.2.1创建组件VNode
_createElement - src\core\vdom\create-element.js
_createElement实际执⾏VNode创建的函数,由于传⼊tag是⾮保留标签,因此判定为⾃定义组件通过
createComponent去创建
createComponent - src/core/vdom/create-component.js
创建组件VNode,保存了上⼀步处理得到的组件构造函数,props,事件等
2.2.2创建组件实例
根组件执⾏更新函数时,会递归创建⼦元素和⼦组件,⼊⼝createElm
createEle() core/vdom/patch.js
⾸次执⾏_update()时,patch()会通过createEle()创建根元素,⼦元素创建研究从这⾥开始
createComponent core/vdom/patch.js
2.3 代码分析
//src/core/instance/render.js
export function initRender (vm: Component) {
...
//通过函数科里化 挂载两个h函数
//这个用于编译器生成
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
//这个用于用户自定义生成
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
}
//src/core/vdom/create-element.js
//参数为 tag data 和child 返回一个vnode
//这个会处理保留的标签和自定义标签
import { createComponent } from './create-component'
export function _createElement (
context: Component,
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array<VNode> {
if (isDef(data) && isDef((data: any).__ob__)) {
process.env.NODE_ENV !== 'production' && warn(
`Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
'Always create fresh vnode data objects in each render!',
context
)
return createEmptyVNode()
}
...
let vnode, ns
//这个判断是否为 字符串标签
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
if (config.isReservedTag(tag)) {//这里是系统保留标签 如 h1 div
// platform built-in elements
if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn) && data.tag !== 'component') {
warn(
`The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
context
)
}
//这里开始实例化vnode
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} else if ((!data || !data.pre) && isDef(
//这里通过resolveAsset 在$options里的components里面拿到对应的信息并返回构造函数
Ctor = resolveAsset(context.$options, 'components', tag))
) {
// component 这个是自定义标签,调用createComponent方法创建
vnode = createComponent(Ctor, data, context, children, tag)
} else {
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
} else {
//处理传入的是 comp组件标签 h(comp)情况
// direct component options / constructor
vnode = createComponent(tag, data, context, children)
}
}
//src/core/vdom/create-component.js
export function createComponent (
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | Array<VNode> | void {
if (isUndef(Ctor)) {
return
}
const baseCtor = context.$options._base
// plain options object: turn it into a constructor
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
// install component management hooks onto the placeholder node
//这里注册各个重要钩子方法
//定义各种钩子init/prepatch/insert/destroy
installComponentHooks(data)
return vnode
}
function installComponentHooks (data: VNodeData) {
const hooks = data.hook || (data.hook = {})
for (let i = 0; i < hooksToMerge.length; i++) {
const key = hooksToMerge[i]
const existing = hooks[key]
const toMerge = componentVNodeHooks[key]
if (existing !== toMerge && !(existing && existing._merged)) {
hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge
}
}
}
const componentVNodeHooks = {
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
//这里是初始化处理
// kept-alive components, treat as a patch
const mountedNode: any = vnode // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode)
} else {
//这里是第一次初始化处理,这个通过vnode转化为组件的实例化
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
//这里会最终挂在子节点到父节点上。但是由于父节点为挂到根的树上,所以还不能显示在页面上
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
},
prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
const options = vnode.componentOptions
const child = vnode.componentInstance = oldVnode.componentInstance
updateChildComponent(
child,
options.propsData, // updated props
options.listeners, // updated listeners
vnode, // new parent vnode
options.children // new children
)
},
insert (vnode: MountedComponentVNode) {
...
},
destroy (vnode: MountedComponentVNode) {
...
}
}
//src/core/vdom/patch.js
export function createPatchFunction (backend) {
...
function createElm (
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
if (isDef(vnode.elm) && isDef(ownerArray)) {
// This vnode was used in a previous render!
// now it's used as a new node, overwriting its elm would cause
// potential patch errors down the road when it's used as an insertion
// reference node. Instead, we clone the node on-demand before creating
// associated DOM element for it.
vnode = ownerArray[index] = cloneVNode(vnode)
}
vnode.isRootInsert = !nested // for transition enter check
//这里通过createComponent 创建对应的组件
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
如果不是自定义组组件 下面正常处理保留标签 如h1 div
const data = vnode.data
const children = vnode.children
const tag = vnode.tag
if (isDef(tag)) {
...
if (__WEEX__) {
...
} else {
//通过createChildren 实现递归继续创建子节点
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
//插入子节点
insert(parentElm, vnode.elm, refElm)
}
}
...
}
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data //vnode.data保留了 前面init初始化的一系列钩子
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
//这里
if (isDef(i = i.hook) && isDef(i = i.init)) {
//执行子组件的实例创建与挂载
i(vnode, false /* hydrating */)//这里创建了dom
}
// after calling the init hook, if the vnode is a child component
// it should've created a child instance and mounted it. the child
// component also has set the placeholder vnode's elm.
// in that case we can just return the element and be done.
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue)
insert(parentElm, vnode.elm, refElm)//插入当前对应的父节点
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
}
2.4流程
整体流程:
new Vue() => $mount() => vm._render() => createElement() => createComponent()
=> vm._update() => patch() => createElm => createComponent()
-
全局注册组件Vue.component(id,{})
-
创建虚拟dom实例化(vnode)createElement() -> createComponent() 创建组件
//src/core/vdom/create-component.js -createComponent() 组件vnode创建 定义各种钩子init/prepatch/insert/destroy -
把vnode 转化为真实的dom,并且与挂载
//src/core/vdom/patch.js -createComponent() 创建组件实例并挂载,vnode转换为dom