Vue源码主要关注了数据驱动,组件化,响应式几个部分。对于源码个人感觉不用太纠结或着急着去看,打好基础使用熟练熟悉之后,再去进一了解会有很大的帮助。我是看huangyi大佬的文章以及一边跟着源码一边加上自己理解而写下来这些内容。
数据驱动(生成获取vnode):
第一篇考虑的是Vue是如何生成节点并挂载的,这里只从以浏览器为平台首次渲染的角度,采用的是runtime+compiler的版本
new Vue(options)
文章图片
很简单,直接调用_init
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判断是否为数组并展平并返回
【Vue源码理解(一)】?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框架的多平台使用。
源码如下:
文章图片
推荐阅读
- vite+vue3+typescript+pnpm-workspace-monorepo 项目搭建记录
- 为啥我用Vue.use(axios)报错了!()
- ts 版 vue 之 vue-property-decorator
- vue中实现 ‘换肤 / 切换样式主题’ 功能的三种方式详解(干货)
- 北京丨蓝湖丨产品设计协同平台丨诚招前端工程师 30K—50K
- 自定义指令实现渐进加载图片