Vue 源码解析(六)之 vm._render
2019-08-22 本文已影响0人
Love小六六
_render函数
- 上一讲我们分析到调用了
vm._update(vm._render(), hydrating)
,那么这一讲我们就先分析 vm._render() 做了什么 - 首先 _render 方法是在哪里定义的呢?在
core/instance/render.js
中
Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render, _parentVnode } = vm.$options
if (_parentVnode) {
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
)
}
vm.$vnode = _parentVnode
let vnode
try {
currentRenderingInstance = vm
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
handleError(e, vm, `render`)
vnode = vm._vnode
} finally {
currentRenderingInstance = null
}
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}
if (!(vnode instanceof VNode)) {
vnode = createEmptyVNode()
}
vnode.parent = _parentVnode
return vnode
}
-
我们可以看到
const { render, _parentVnode } = vm.$options
先从 $options 中拿到了render -
然后执行了 render,并传参 vm.$createElement, 从而获取到 vnode 并返回
// vm._renderProxy就是vm,忽略不分析
vnode = render.call(vm._renderProxy, vm.$createElement)
vnode
- dom元素是非常庞大的,我们频繁去做dom更新就会产生一定的性能问题
- Vnode 就是用原生的 js 对象去描述 DOM 节点,它借鉴了开源库snabbdom ,在 flow 文件夹下可以看到 VNode 的定义,,对 vnode 定义了一些关键属性如标签名、数据、子节点等,用来映射到真实 DOM 的渲染,因此轻量简单
- Vnode 映射到真实 DOM 需要经历创建 diff patch等过程
- Vnode 是如何创建的呢? 就是通过我们之前分析的 vm.$createElement 函数
vm.$createElement 是什么
- 在 function Vue 阶段做了各种 mixin , 其中有 initRender, initRender 中包括这样一段代码
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
- 也就是说我们的 vm.$createElement 实际上是去执行了 createElement 操作
createElement
- 在
core/vdom/create-element.js
中
export function createElement (
context: Component, // vm 实例
tag: any, // 标签
data: any, // 数据
children: any, // 子节点,从而构建出 vnode tree
normalizationType: any,
alwaysNormalize: boolean
): VNode | Array<VNode> {
if (Array.isArray(data) || isPrimitive(data)) {
normalizationType = children
// 没有 data 时参数前移
children = data
data = undefined
}
if (isTrue(alwaysNormalize)) {
normalizationType = ALWAYS_NORMALIZE
}
return _createElement(context, tag, data, children, normalizationType)
}
- 我们发现它实际上是对参数做了一层封装,然后调用 _createElement 方法
_createElement
- 我把这部分的代码精简了一下,保留了核心的部分
export function _createElement (
context: Component,
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array<VNode> {
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
let vnode, ns
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
if (config.isReservedTag(tag)) {
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
vnode = createComponent(Ctor, data, context, children, tag)
} else {
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
} else {
vnode = createComponent(tag, data, context, children)
}
if (Array.isArray(vnode)) {
return vnode
} else if (isDef(vnode)) {
if (isDef(ns)) applyNS(vnode, ns)
if (isDef(data)) registerDeepBindings(data)
return vnode
} else {
return createEmptyVNode()
}
}
- 我们会发现这个函数先对 children 做了 normalize, 因为children 可能是数组类型,normalize方法,normalizeArrayChildren 主要的逻辑是遍历 children,获得单个节点 c, 然后判断 c 的类型,如果是数组类型,则递归调用 normalizeArrayChildren,如果是基础类型,则通过 createTextVNode 转化为 VNode 类型(其中做了优化,两个连续的 text 节点会合并成一个 text 节点),变成了一个一维数组
- 我们现在 demo 的 tag 是 string 类型,因此会走到 vnode = new Vnode()从而生成一个 vnode
demo 调试
- 调用 _createElement
- _createElement 创建了vnode
- 因此 _render 最终返回了一个 vnode