第六部分:Vue3源码学习

144 阅读4分钟

1.真实DOM渲染

我们传统的前端开发中,我们是编写自己的HTML,最终被渲染到浏览器上的,那么它是什么样的过程呢?

19_Vue3源码学习_页面_02_图像_0001.jpg

虚拟DOM的优势

目前框架都会引入虚拟DOM来对真实的DOM进行抽象,这样做有很多的好处:

首先是可以对真实的元素节点进行抽象,抽象成VNode(虚拟节点),这样方便后续对其进行各种操作:
因为对于直接操作DOM来说是有很多的限制的,比如diff、clone等等,但是使用JavaScript编程语言来操作这些,就变得非常的简单;
我们可以使用JavaScript来表达非常多的逻辑,而对于DOM本身来说是非常不方便的;

其次是方便实现跨平台,包括你可以将VNode节点渲染成任意你想要的节点
如渲染在canvas、WebGL、SSR、Native(iOS、Android)上;
并且Vue允许你开发属于自己的渲染器(renderer),在其他的平台上渲染;

虚拟DOM的渲染过程

19_Vue3源码学习_页面_04_图像_0001.jpg

19_Vue3源码学习_页面_04_图像_0002.jpg

三大核心系统

事实上Vue的源码包含三大核心:
Compiler模块:编译模板系统;
Runtime模块:也可以称之为Renderer模块,真正渲染的模块;
Reactivity模块:响应式系统;

19_Vue3源码学习_页面_05_图像_0001.jpg

19_Vue3源码学习_页面_05_图像_0002.jpg

三大系统协同工作

三个系统之间如何协同工作呢:

19_Vue3源码学习_页面_06_图像_0001.jpg

实现Mini-Vue

这里我们实现一个简洁版的Mini-Vue框架,该Vue包括三个模块:
渲染系统模块;
可响应式系统模块;
应用程序入口模块;

2.渲染器实现

渲染系统,该模块主要包含三个功能:
功能一:h函数,用于返回一个VNode对象;
功能二:mount函数,用于将VNode挂载到DOM上;
功能三:patch函数,用于对两个VNode进行对比,决定如何处理新的VNode;

h函数– 生成VNode

h函数的实现:直接返回一个VNode对象即可

20_Vue3源码学习(二)_页面_09_图像_0001.jpg

Mount函数– 挂载VNode

mount函数的实现:

第一步: 根据tag,创建HTML元素,并且存储 到vnode的el中;

第二步: 处理props属性
如果以on开头,那么监听事件;
普通属性直接通过setAttribute 添加即可;

第三步: 处理子节点
如果是字符串节点,那么直接设置textContent;
如果是数组节点,那么遍历调用mount 函数;

20_Vue3源码学习(二)_页面_10_图像_0001.jpg

Patch函数– 对比两个VNode

patch函数的实现,分为两种情况

n1和n2是不同类型的节点:
找到n1的el父节点,删除原来的n1节点的el;
挂载n2节点到n1的el父节点上;

n1和n2节点是相同的节点:

  • 处理props的情况
    √先将新节点的props全部挂载到el上;
    √判断旧节点的props是否不需要在新节点上,如果不需要,那么删除对应的属性;
  • 处理children的情况
    √如果新节点是一个字符串类型,那么直接调用el.textContent = newChildren;
    √如果新节点不同一个字符串类型:

旧节点是一个字符串类型

  1. 将el的textContent设置为空字符串;
  2. 就节点是一个字符串类型,那么直接遍历新节点,挂载到el上;
    旧节点也是一个数组类型
  3. 取出数组的最小长度;
  4. 遍历所有的节点,新节点和旧节点进行path操作;
  5. 如果新节点的length更长,那么剩余的新节点进行挂载操作;
  6. 如果旧节点的length更长,那么剩余的旧节点进行卸载操作;

20_Vue3源码学习(二)_页面_12_图像_0001.jpg

20_Vue3源码学习(二)_页面_12_图像_0002.jpg

依赖收集系统

20_Vue3源码学习(二)_页面_13_图像_0001.jpg

20_Vue3源码学习(二)_页面_13_图像_0002.jpg

3.响应式系统

响应式系统Vue2实现

20_Vue3源码学习(二)_页面_14_图像_0001.jpg

20_Vue3源码学习(二)_页面_14_图像_0002.jpg

响应式系统Vue3实现

20_Vue3源码学习(二)_页面_15_图像_0001.jpg

为什么Vue3选择Proxy呢?

Object.definedProperty 是劫持对象的属性时,如果新增元素:
那么Vue2需要再次调用definedProperty,而Proxy 劫持的是整个对象,不需要做特殊处理;

修改对象的不同:
使用defineProperty 时,我们修改原来的obj 对象就可以触发拦截;
而使用proxy,就必须修改代理对象,即Proxy 的实例才可以触发拦截;

Proxy 能观察的类型比defineProperty 更丰富
has:in操作符的捕获器;
deleteProperty:delete 操作符的捕捉器;
等等其他操作;

Proxy 作为新标准将受到浏览器厂商重点持续的性能优化;

缺点:Proxy 不兼容IE,也没有polyfill, defineProperty 能支持到IE9

框架外层API设计

这样我们就知道了,从框架的层面来说,我们需要有两部分内容:
createApp用于创建一个app对象;
该app对象有一个mount方法,可以将根组件挂载到某一个dom元素上;

20_Vue3源码学习(二)_页面_17_图像_0001.jpg

源码阅读之createApp

20_Vue3源码学习(二)_页面_18_图像_0001.jpg

源码阅读之挂载根组件

20_Vue3源码学习(二)_页面_19_图像_0001.jpg

const app = {props: {message: String}
instance
// 1.处理props和attrs
instance.props
instance.attrs
// 2.处理slots
instance.slots
// 3.执行setup
const result = setup()
instance.setupState = proxyRefs(result);
// 4.编译template -> compile
<template> -> render函数
instance.render = Component.render = render函数
// 5.对vue2的options api进行知识
data/methods/computed/生命周期

组件化的初始化

20_Vue3源码学习(二)_页面_21_图像_0001.jpg

Compile过程

20_Vue3源码学习(二)_页面_22_图像_0001.jpg

Block Tree分析

20_Vue3源码学习(二)_页面_23_图像_0001.jpg

生命周期回调

20_Vue3源码学习(二)_页面_24_图像_0001.jpg

template中数据的使用顺序

20_Vue3源码学习(二)_页面_25_图像_0001.jpg