Vue源码主要关注了数据驱动,组件化,响应式几个部分。对于源码个人感觉不用太纠结或着急着去看,打好基础使用熟练熟悉之后,再去进一了解会有很大的帮助。我是看huangyi大佬的文章以及一边跟着源码一边加上自己理解而写下来这些内容。
数据驱动(生成获取vnode):
第一篇考虑的是Vue是如何生成节点并挂载的,这里只从以浏览器为平台首次渲染的角度,采用的是runtime+compiler的版本
new Vue(options)
this._init(options)
该函数主要调用配置合并函数,同时进行各种初始化工作,然后调用vm.$mount
进行挂载,重点看$mount
vm.$mount(el, hydrating)
(挂载函数,在platform文件夹内,因为内部的具体实现与平台相关,hydrating参数正是与服务端渲染相关,所以在这不用考虑)
1 先用mount
缓存原$mount
(platform/web/runtime/index),再重新定义$mount
2 新的$mount
内部通过compileToFunctions
获取render
以及staticRenderFns
函数(或通过sfc(vue-loader解析)获取render函数),这是在runtime+compiler版本中然后返回原mount
调用结果。
mount(el, hydrating)(原$mount)
原mount函数很简单,主要就是调用mountComponent(this,el,hydrating)
函数
mountComponent(this,el,hydrating)
步骤:
1 触发beforemount
钩子函数
2 定义updateComponent
函数,函数内部调用实例的vm._update(vm._render(), hydrating)
函数中的vm._render()
实际上是调用render
函数而生成一个的vnode(见下)
3 new Watcher(vm,updateComponent,noop,{ before },true)
生成watcher实例,updateComponent
会在watcher实例生成与更新的时候触发,传入的before函数仅仅是一个钩子,这里函数内部就是调用一下组件的beforeUpdate钩子函数而已
4 设置实例_isMounted
为true,并调用mounted钩子函数
5 返回实例vm
_render()
核心部分便是vnode = render.call(vm._renderProxy, vm.$createElement)
,然后返回生成的vnode
render()
render函数生成比较复杂,根据不同的环境生成,但是最终是通过调用我们常见的createElement
(也叫h函数)生成了vnode
$createElement = createElement(vm,a,b,c,d,true)
在initRender函数中的createElement函数,即是对_createElement函数的封装,在对传入的参数处理后调用 _createElement(context, tag, data, children, normalizationType)
_createElement(context, tag, data, children, normalizationType)
关注两个重点流程 children 的规范化以及 VNode 的创建。
1 children规范化(生成vnode数组):
当normalizationType为ALWAYS_NORMALIZE: normalizeChildren(children)
当normalizationType为SIMPLE_NORMALIZE: simpleNormalizeChildren(children)
simpleNormalizeChildren:默认内部生成的children都是Vnode类型(调用场景为编译生成render),只有函数式组件会生成一个数组。所以处理很简单,就是遍历children判断是否为数组并展平并返回
normalizeChildren:方法的调用场景有 2 种,一个场景是 render
函数是用户手写的,当 children
只有一个节点的时候,Vue.js 从接口层面允许用户把 children
写成基础类型用来创建单个简单的文本节点,这种情况会调用 createTextVNode
创建一个文本节点的 VNode;另一个场景是当编译 slot
、v-for
的时候会产生嵌套数组的情况,会调用 normalizeArrayChildren
方法
normalizeArrayChildren:该方法便是遍历children转换为vnode,如果遇到数组,则递归调用本函数
注意:如果存在两个连续的 text
节点,会把它们合并成一个 text
节点
2 Vnode 的创建
如果tag为string类型:
1 为通常tag,则直接new Vnode
2 否则检测tag是否为已注册的component tag,是的话调用createComponent
3 否则创建一个未知标签的vnode
如果tag为component类型:
component类型后续会有说明: 直接创建一个component类型vnode
3 返回生成的vnode
采用vnode的好处有很多,一方面可以不用直接使用原生厚重的dom,虚拟节点更加轻量,舍弃了很多dom的属性方法,更加定制化。其次还能用于vue框架的多平台使用。
源码如下: