vue3自定义实现
# 总体把握
主体
- Vue3开发环境搭建,手写Vue3响应式模块
- 手写Vue3初始化流程实现、虚拟DOM实现、组件实现
- 手写
Vue3diff
算法实现 - 手写
watchApi
异步渲染 、生命周期实现 、vue中AST
转化 (额外:vue3 组件库搭建) - 手写模板编译 transform 核心逻辑
- 手写模板编译template -> render函数逻辑
- Vue3 源码剖析及调试技巧 + 其他
api
实现原理 + 单元测试
其他
- ts课程:类、接口、泛型、兼容性、条件、类型保护、声明文件.....等
- vue3项目实战
- vue3其他生态源码剖析
vue-router
、vuex
、vite
# 架构分析/环境搭建
# 区别介绍
- 源码采用
monorepo
方式进行管理,将模块拆分到package目录中; Vue3
采用ts
开发,增强类型检测。Vue2
则采用flow
;Vue3
的性能优化,支持tree-shaking, 不使用就不会被打包;Vue2
后期引入RFC , 使每个版本改动可控 rfcs (opens new window);
内部代码优化
Vue3
劫持数据采用proxyVue2
劫持数据采用defineProperty
。defineProperty
有性能问题和缺陷;Vue3
中对模板编译进行了优化,编译时 生成了Block tree,可以对子节点的动态节点进行收集,可以减少比较,并且采用了patchFlag
标记动态节点;Vue3
采用compositionApi
进行组织功能,解决反复横跳,优化复用逻辑 (mixin带来的数据来源不清晰、命名冲突等), 相比optionsApi
类型推断更加方便;- 增加了
Fragment
,Teleport
,Suspense
组件;
# Monorepo
介绍
Monorepo
是管理项目代码的一个方式,指在一个项目仓库(repo
)中管理多个模块/包(package)
- 一个仓库可维护多个模块,不用到处找仓库;
- 方便版本管理和依赖管理,模块之间的引用,调用都非常方便;
缺点:仓库体积会变大。
# 项目结构
reactivity
:响应式系统runtime-core
:与平台无关的运行时核心 (可以创建针对特定平台的运行时 - 自定义渲染器)runtime-dom
: 针对浏览器的运行时。包括DOM API
,属性,事件处理等runtime-test
:用于测试server-renderer
:用于服务器端渲染compiler-core
:与平台无关的编译器核心compiler-dom
: 针对浏览器的编译模块compiler-ssr
: 针对服务端渲染的编译模块compiler-sfc
: 针对单文件解析size-check
:用来测试代码体积template-explorer
:用于调试编译器输出的开发工具shared
:多个包之间共享的内容vue
:完整版本,包括运行时和编译器
# 安装依赖
依赖 | |
---|---|
typescript | 支持typescript |
rollup | 打包工具 |
rollup-plugin-typescript2 | rollup 和 ts的 桥梁 |
@rollup/plugin-node-resolve | 解析node第三方模块 |
@rollup/plugin-json | 支持引入json |
execa | 开启子进程方便执行命令 |
npm init -y && npx tsc --init
npm install typescript rollup rollup-plugin-typescript2 @rollup/plugin-node-resolve @rollup/plugin-json execa -D
2
# workspace
配置
npm init -y && npx tsc --init
其实这个是对应lerna.json
的配置; lerna init
生成的; 【lerna相关不用设置】
{
"packages": [
"packages/*"
],
"version": "0.0.0"
}
2
3
4
5
6
把这个配置lerna合并到pkg中;
package.json
配置
{
"private":true,
"workspaces":[
"packages/*"
],
}
2
3
4
5
6
目录结构配置
C:.
│ package.json # 配置运行命令
│ rollup.config.js # rollup配置文件
│ tsconfig.json # ts配置文件 更改为esnext
│ yarn.lock
│
├─packages # N个repo
│ └─reactivity
│ │ package.json
│ └─src
│ index.ts
│
└─scripts # 打包命令
build.js
2
3
4
5
6
7
8
9
10
11
12
13
14
配置模块名称及打包选项; 创建软链
yarn install
{
"name": "@vue/reactivity",
"version": "1.0.0",
"description": "",
"main": "index.js",
"module": "dist/reactivity.esm-bundler.js",
"author": "",
"license": "ISC",
"buildOptions":{
"name":"VueReactivity",
"formats":[
"esm-bundler",
"cjs",
"global"
]
},
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
配置
tsconfig.json
识别引入第三方模块
{
"baseUrl": ".",
"moduleResolution": "node",
"paths": {
"@vue/*": [
"packages/*/src"
]
}
}
2
3
4
5
6
7
8
9
# 响应式原理
# 响应式API
实现
//使用示范:
const { reactive, shallowReactive, readonly, shallowReadonly } = VueReactivity
let obj = { name: 'sy', age: { n: 11 } }
const state = reactive(obj)
const state = shallowReactive(obj)
const state = readonly(obj)
const state = shallowReadonly(obj)
2
3
4
5
6
7
# reactive响应式实现
针对不同的
API
创建不同的响应式对象
import {
mutableHandlers,
readonlyHandlers,
shallowReactiveHandlers,
shallowReadonlyHandlers
} from "./baseHandlers"; // 不同的拦截函数
export function reactive(target) {
return createReactiveObject(target, false, mutableHandlers)
}
export function shallowReactive(target) {
return createReactiveObject(target, false, shallowReactiveHandlers)
}
export function readonly(target) {
return createReactiveObject(target, true, readonlyHandlers)
}
export function shallowReadonly(target) {
return createReactiveObject(target, true, shallowReadonlyHandlers)
}
/**
* @param target 拦截的目标
* @param isReadonly 是不是仅读属性
* @param baseHandlers 对应的拦截函数
*/
function createReactiveObject(target, isReadonly, baseHandlers) {}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
shared
模块封装
使用
yarn install
将shared
模块注入到node_modules中
{
"name": "@vue/shared",
"version": "1.0.0",
"description": "",
"main": "index.js",
"module": "dist/shared.esm-bundler.js",
"buildOptions": {
"formats": [
"esm-bundler",
"cjs"
]
},
"keywords": [],
"author": "",
"license": "ISC"
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# createReactiveObject
Vue3中采用proxy实现数据代理, 核心就是拦截
get
方法和set
方法,当获取值时收集effect
函数,当修改值时触发对应的effect重新执行;
import { isObject } from '@vue/shared'
const reactiveMap = new WeakMap();
const readonlyMap = new WeakMap();
function createReactiveObject(target, isReadonly, baseHandlers) {
// 1.如果不是对象直接返回
if(!isObject(target)){
return target
}
const proxyMap = isReadonly ? readonlyMap : reactiveMap; // 获取缓存对象
const existingProxy = proxyMap.get(target);
// 2.代理过直接返回即可
if(existingProxy){
return existingProxy;
}
// 3.代理的核心
const proxy = new Proxy(target,baseHandlers);
proxyMap.set(target,proxy);
// 4.返回代理对象
return proxy;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# baseHandlers
实现
import { isObject } from "@vue/shared";
import { reactive, readonly } from "./reactive";
const get = createGetter();
const shallowGet = createGetter(false, true);
const readonlyGet = createGetter(true);
const shallowReadonlyGet = createGetter(true, true)
const set = createSetter();
const shallowSet = createSetter(true);
/**
* @param isReadonly 是不是仅读
* @param shallow 是不是浅响应
*/
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
if (!isReadonly) { // 如果是仅读的无需收集依赖
console.log('依赖收集')
}
if (shallow) { // 浅无需返回代理
return res
}
if (isObject(res)) { // 取值时递归代理
return isReadonly ? readonly(res) : reactive(res)
}
return res;
}
}
function createSetter(shallow = false) {
return function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
return result;
}
}
export const mutableHandlers = {
get,
set
};
export const readonlyHandlers = {
get: readonlyGet,
set(target, key) {
console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`)
return true;
}
};
export const shallowReactiveHandlers = {
get: shallowGet,
set: shallowSet
};
export const shallowReadonlyHandlers = {
get: shallowReadonlyGet,
set(target, key) {
console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`)
return true;
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# effect实现
# effect响应式实现
export function effect(fn, options: any = {}) {
// 创建响应式effect
const effect = createReactiveEffect(fn, options);
// 默认会让effect先执行一次
if (!options.lazy) {
effect();
}
return effect
}
let uid = 0;
function createReactiveEffect(fn, options) {
// 返回响应式effect
const effect = function reactiveEffect() {
// todo...
}
effect.id = uid++;// 用于做标识的
effect._isEffect = true; // 标识是响应式effect
effect.raw = fn; // 记录原本的fn
effect.deps = []; // 用于收集effect对应的相关属性
effect.options = options;
return effect;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
利用栈型结构存储effect,保证依赖关系
const effect = function reactiveEffect() {
if (!effectStack.includes(effect)) {
try {
effectStack.push(effect);
activeEffect = effect; // 记录当前的effect
return fn(); // 执行用户传递的fn -> 取值操作
} finally {
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
}
}
}
2
3
4
5
6
7
8
9
10
11
12
//使用示范:
const state =reactive({name:'sy',age:12,address:'回龙观'})
effect(()=>{ // effect1
console.log(state.name); // 收集effect1
effect(()=>{ // effect2
console.log(state.age); // 收集effect2
});
console.log(state.address); // 收集effect1
})
2
3
4
5
6
7
8
9
# track
依赖收集
function createGetter(isReadonly = false, shallow = false) {
return function get(target, key, receiver) {
// ...
if (!isReadonly) { // effect函数执行时,进行取值操作,让属性记住对应的effect函数
track(target, TrackOpTypes.GET, key);
}
}
}
2
3
4
5
6
7
8
const targetMap = new WeakMap();
export function track(target, type, key) {
if(activeEffect === undefined){ // 如果不在effect中取值,则无需记录
return
}
let depsMap = targetMap.get(target);
// WeakMap({name:'sy',age:11},{name:{Set},age:{Set}})
if(!depsMap){ // 构建存储结构
targetMap.set(target,(depsMap = new Map))
}
let dep = depsMap.get(key);
if(!dep){
depsMap.set(key,(dep = new Set));
}
if(!dep.has(activeEffect)){
dep.add(activeEffect);
//activeEffect.deps.push(dep);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# trigger
触发更新
对新增属性和修改属性做分类
function createSetter(shallow = false) {
return function set(target, key, value, receiver) {
const oldValue = target[key];
const hadKey =
isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key);
const result = Reflect.set(target, key, value, receiver);
if (!hadKey) {
// 新增属性
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
// 修改属性
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
return result;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
将需要触发的effect找到依次执行
export function trigger(target, type, key?, newValue?, oldValue?) {
const depsMap = targetMap.get(target);
if (!depsMap) { // 属性没有对应的effect
return
}
const effects = new Set(); // 设置集合
const add = (effectsToAdd) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => effects.add(effect))
}
}
if (key === 'length' && isArray(target)) { // 如果修改的是长度
depsMap.forEach((dep, key) => {
// 如果有长度的依赖要更新 如果依赖的key小于设置的长度也要更新
if (key == 'length' || key >= newValue) {
add(dep)
}
});
} else {
if (key !== void 0) {
// 修改key
add(depsMap.get(key));
}
switch (type) {
case TriggerOpTypes.ADD:
if (isArray(target)) {
if (isIntegerKey(key)) { // 给数组新增属性,直接触发length即可
add(depsMap.get('length'))
}
}
break;
default:
break;
}
}
effects.forEach((effect: any) => {
effect();
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# Ref相关实现
# 实现Ref
ref本质就是通过类的属性访问器来实现的,可以将一个普通值类型进行包装。
import { hasChanged, isObject } from "@vue/shared";
import { track, trigger } from "./effect";
import { TrackOpTypes, TriggerOpTypes } from "./operations";
import { reactive } from "./reactive";
export function ref(value) { // ref Api
return createRef(value);
}
export function shallowRef(value) { // shallowRef Api
return createRef(value, true);
}
function createRef(rawValue, shallow = false) {
return new RefImpl(rawValue, shallow)
}
const convert = (val) => isObject(val) ? reactive(val) : val; // 递归响应式
class RefImpl {
private _value;
public readonly __v_isRef = true; // 标识是ref
constructor(private _rawValue, public readonly _shallow) {
this._value = _shallow ? _rawValue : convert(_rawValue)
}
get value() {
track(this, TrackOpTypes.GET, 'value');
return this._value;
}
set value(newVal) {
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal; // 保存值
this._value = this._shallow ? newVal : convert(newVal);
trigger(this, TriggerOpTypes.SET, 'value', newVal);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 实现toRef
/ toRefs
export function toRef(object,key){
return new ObjectRefImpl(object,key);
}
export function toRefs(object) {
const ret = isArray(object) ? new Array(object.length) : {};
for (const key in object) {
ret[key] = toRef(object, key)
}
return ret;
}
class ObjectRefImpl{
public readonly __v_isRef = true
constructor(private readonly _object, private readonly _key) {}
get value(){
return this._object[this._key]
}
set value(newVal){
this._object[this._key] = newVal
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
将对象中的属性转换成ref属性
# Computed
实现
computed的整体思路和
Vue2.0
源码基本一致,也是基于缓存来实现的。
import { effect, track, trigger } from "./effect";
import { isFunction } from "@vue/shared";
import { TrackOpTypes, TriggerOrTypes } from "./operators";
// vue2 和 vue3 computed原理是不一样的
export function computed(getterOrOptions) {
// 分别拿到get和set
let getter;
let setter;
// computed两种写法
if (isFunction(getterOrOptions)) {
getter = getterOrOptions;
setter = () => {
// console.log("computed not set value");
console.warn("computed value must be readonly");
};
} else {
getter = getterOrOptions.get;
setter = getterOrOptions.set;
}
return new ComputedRefImpl(getter, setter);
}
class ComputedRefImpl {
public _dirty = true; // 默认是脏值,默认取值时不要用缓存
private _value;
public readonly effect;
public __v_isReadonly = true;
public readonly __v_isRef = true;
public setter;
constructor(getter, setter) {
this.setter = setter;
// 默认getter执行的时候会依赖于一个effect (计算属性默认就是一个effect)
this.effect = effect(getter, {
lazy: true, // 计算属性特性;标识默认的时候 不会执行
scheduler: () => {
// 依赖属性变化时
if (!this._dirty) {
// 默认就算属性依赖的值变化会执行scheduler方法
this._dirty = true; // 标记为脏值,触发视图更新
trigger(this, "set", "value");
}
},
});
}
// 计算属性也要收集依赖
get value() {
// 类属性描述器
if (this._dirty) {
// 缓存
this._value = this.effect(); // 取值时执行effect
// track(this, "value"); // 取值时收集依赖 {this:{'value':[effect]}
track(this, TrackOpTypes.GET, "value"); // 进行属性依赖收集
this._dirty = false;
}
return this._value;
}
set value(newValue) {
// 自动去调用用户的set即可 myAge.value = xxx
this.setter(newValue);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
scheduler支持
effect.ts
effects.forEach((effect: any) => {
if(effect.options.scheduler){
return effect.options.scheduler(effect); // 如果有自己提供的scheduler,则执行scheduler逻辑
}
effect();
})
2
3
4
5
6
# 初始化流程
# 虚拟DOM转真实DOM
使用示范:
先创建一个虚拟节点对象,调用render方法将虚拟节点转化为真实节点。
let { render } = Vue
const state = { count: 0 };
const vnode = {
tag: 'div',
props: {
style: { color: 'red' }
},
children: [{
tag: 'p',
props: null,
children: `vue@3- 计数器 ${state.count}`
}, {
tag: 'button',
props: {
onClick: () => alert(state.count)
},
children: '点我啊'
}]
}
render(vnode, app);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 介绍 VueRuntimeDOM
Vue中将runtime模块分为
runtime-core
核心代码 及 其他平台对应的运行时,那么VueRuntimeDOM无疑就是解决浏览器运行时的问题,此包中提供了DOM 属性操作和节点操作一系列接口。runtime-dom
和runtime-core
模块解析;
# patchProp实现
此方法主要针对不同的属性提供不同的patch操作
import { patchClass } from "./modules/class"; // 类名处理
import { patchStyle } from "./modules/style"; // 样式处理
import { patchEvent } from "./modules/events"; // 事件处理
import { patchAttr } from "./modules/attrs"; // 属性处理
import {isOn} from '@vue/shared';
export const patchProp = (el,key,prevValue,nextValue) => {
switch (key) {
// 先处理特殊逻辑
case 'class':
patchClass(el,nextValue)
break
case 'style':
patchStyle(el,prevValue,nextValue)
break;
default:
if(isOn(key)){
// 如果是事件
patchEvent(el,key,nextValue)
}else{
patchAttr(el,key,nextValue);
}
break;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
patchClass
export const patchClass = (el,value)=>{
if(value == null){
value = '';
}
el.className = value;// 设置样式名
}
2
3
4
5
6
patchStyle
export const patchStyle = (el, prev, next) => {
const style = el.style;
if (!next) {
el.removeAttribute('style');
} else {
for (const key in next) {
style[key] = next[key];
}
if (prev) {
for (const key in prev) {
if (next[key] == null) {
style[key] = '';
}
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
patchEvent
export const patchEvent = (el, rawName, nextValue) => {
const invokers = el._vei || (el._vei = {});
const exisitingInvoker = invokers[rawName];
if (nextValue && exisitingInvoker) { // 如果绑定过,则替换为新的
exisitingInvoker.value = nextValue;
} else {
const name = rawName.slice(2).toLowerCase();
if (nextValue) { // 绑定新值
const invoker = (invokers[rawName] = createInvoker(nextValue));
el.addEventListener(name, invoker);
} else if (exisitingInvoker) {
el.removeEventListener(name, exisitingInvoker);
invokers[rawName] = undefined;
}
}
}
function createInvoker(initialValue) {
const invoker = (e) => {
invoker.value(e);
}
invoker.value = initialValue;
return invoker;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
patchAttr
export const patchAttr = (el, key, value) => {
if (value == null) {
el.removeAttribute(key);
} else {
el.setAttribute(key, value);
}
}
2
3
4
5
6
7
# nodeOps实现
实现DOM操作方法
这里存放着所有的节点操作的方法
export const nodeOps = {
insert: (child, parent, anchor) => { // 增加
parent.insertBefore(child, anchor || null)
},
remove: child => { // 删除
const parent = child.parentNode
if (parent) {
parent.removeChild(child);
}
},
// 创建元素
createElement: tag => document.createElement(tag),
// 创建文本
createText: text => document.createTextNode(text),
// 设置元素内容
setElementText: (el, text) => {
el.textContent = text
},
// 设置文本内容
setText: (node, text) => {
node.nodeValue = text
},
parentNode: node => node.parentNode, // 获取父节点
nextSibling: node => node.nextSibling, // 获取下个兄弟
querySelector: selector => document.querySelector(selector),
hostPatchProps: (el, key, value) => {
const onRe = /^on[^a-z]/;
const isOn = (key) => onRe.test(key);
if (isOn(key)) {
// 事件添加
const name = key.slice(2).toLowerCase();
el.addEventListener(name, value);
} else {
if (key === "style") {
// 样式处理
for (let key in value) {
el.style[key] = value[key];
}
} else {
el.setAttribute(key, value);
}
}
},
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# runtimeDom实现【要点】
用户调用的createApp函数就在这里被声明
// runtime-dom主要提供dom操作的方法
import { extend } from "@vue/shared"
import { createRenderer } from "@vue/runtime-core";
import { patchProp } from './patchProp'
import { nodeOps } from './nodeOps'
// runtimeDom中对dom操作的所有选项
const rendererOptions = extend({ patchProp }, nodeOps);
// 用户调用的createApp方法,此时才会创建渲染器
export const createApp = (rootComponent, rootProps = null) => {
const app = createRenderer(rendererOptions).createApp(rootComponent, rootProps);
const { mount } = app;
app.mount = function (container) {
container = document.querySelector(container);
container.innerHTML = ''; // 清空容器内容
const proxy = mount(container); // 执行挂载逻辑
return proxy;
}
return app
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// -----------这些逻辑移动到core中与平台代码无关--------------
function createRenderer(rendererOptions) {
return {
createApp(rootComponent, rootProps) { // 用户创建app的参数
const app = {
mount(container) { // 挂载的容器
}
}
return app;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
# runtimeCore实现
renderer.ts
import { createAppAPI } from "./apiCreateApp"
export function createRenderer(rendererOptions) { // 渲染时所到的api
const render = (vnode,container) =>{ // 核心渲染方法
// 将虚拟节点转化成真实节点插入到容器中
}
return {
createApp:createAppAPI(render)
}
}
2
3
4
5
6
7
8
9
apiCreateApp.ts
(ast => codegen) => render => vnode => dom
export function createAppAPI(render){
return function createApp(rootComponent,rootProps = null){
const app = {
_props:rootProps, // 属性
_component:rootComponent, // 组件
_container:null,
mount(rootContainer){
// 1.通过rootComponent 创建vnode
// 2.调用render方法将vnode渲染到rootContainer中
}
}
return app;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# VNode实现
优化版本
import { createVNode } from "./vnode";
export function createAppAPI(render){
return function createApp(rootComponent,rootProps = null){
const app = {
_props:rootProps, // 属性
_component:rootComponent, // 组件
_container:null,
mount(rootContainer){
// 1.通过rootComponent 创建vnode
// 2.调用render方法将vnode渲染到rootContainer中
const vnode = createVNode(rootComponent,rootProps);
render(vnode,rootContainer);
app._container = rootContainer
}
}
return app;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
vnode.ts
创建出vnode,交给render函数进行渲染;
import { isObject, isString, ShapeFlags } from "@vue/shared/src"
export const createVNode = (type, props, children = null) => {
const shapeFlag = isString(type) ?
ShapeFlags.ELEMENT
: isObject(type) ?
ShapeFlags.STATEFUL_COMPONENT
: 0
const vnode = {
type,
props,
children,
key: props && props.key, // 用于diff算法
el: null, // 虚拟节点对应的真实节点
shapeFlag // 自己是什么类型
}
normalizeChildren(vnode, children); // 根据子节点计算孩子类型
return vnode
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function normalizeChildren(vnode, children) {
let type = 0;
if(children == null){
}else if(isArray(children)){
type = ShapeFlags.ARRAY_CHILDREN; // 数组
}else{
type = ShapeFlags.TEXT_CHILDREN; // 文本
}
vnode.shapeFlag |= type
}
2
3
4
5
6
7
8
9
10
11
# 渲染流程
# 初始化渲染逻辑
初次调用render方法时,虚拟节点的类型为组件
const processElement = (n1, n2, container) => {
}
const mountComponent = (initialVNode, container) => {
// 组件初始化
}
const processComponent = (n1, n2, container) => {
if (n1 == null) {
mountComponent(n2, container)
}
}
const patch = (n1, n2, container) => {
const { shapeFlag } = n2;
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(n1, n2, container); // 处理元素类型
} else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
processComponent(n1, n2, container); // 处理组件类型
}
}
const render = (vnode, container) => {
patch(null, vnode, container); // 初始化逻辑老的虚拟节点为null
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 组件渲染流程
# 为组件创造实例
const mountComponent = (initialVNode, container) => {
const instance = (initialVNode.component = createComponentInstance(
initialVNode
))
}
2
3
4
5
export function createComponentInstance(vnode) {
const type = vnode.type;
const instance = { // 组件实例
__v_isVNode: true,
vnode, // 组件对应的虚拟节点
subTree: null, // 组件要渲染的子元素
type, // 组件对象
ctx: {}, // 组件的上下文
props: {}, // 组件的属性
attrs: {}, // 元素本身的属性
slots: {}, // 组件的插槽
setupState: {}, // 组件setup的返回值
isMounted: false // 组件是否被挂载?
}
instance.ctx = { _: instance };
return instance
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 扩展instance
需要给instance上的属性进行初始化操作
const mountComponent = (initialVNode, container) => {
// 1.创建组件实例
const instance = (initialVNode.component = createComponentInstance(
initialVNode
))
// 2.给instance赋值
setupComponent(instance)
}
2
3
4
5
6
7
8
组件的启动,核心就是调用setup方法
export function isStatefulComponent(instance) {
return instance.vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT
}
export function setupComponent(instance) {
// 获取虚拟节点属性和插槽的内容
const { props, children } = instance.vnode;
// 1.初始化属性
// 2.初始化插槽
const isStateful = isStatefulComponent(instance)
// 获取setup函数的返回值
const setupResult = isStateful ? setupStatefulComponent(instance) : undefined;
return setupResult
}
2
3
4
5
6
7
8
9
10
11
12
13
14
提供instance.proxy, 代理实例上一系列属性
import { hasOwn } from "@vue/shared";
export const PublicInstanceProxyHandlers = {
get({ _: instance }, key) { // 做代理
const { setupState } = instance;
if (key[0] !== '$') {
// 说明访问的时普通属性 不以$开头
if (hasOwn(setupState, key)) {
return setupState[key];
}
}
},
set({ _: instance }, key, value) {
const {setupState} = instance;
if(hasOwn(setupState,key)){
setupState[key] = value;
}
return true;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let currentInstance;
function setupStatefulComponent(instance) {
// 通过instance.proxy 访问实例
instance.proxy = new Proxy(instance.ctx,PublicInstanceProxyHandlers);
const Component = instance.type;
const {setup} = Component;
if(setup){
currentInstance = instance;
const setupContext = createSetupContext(instance); // setup中的上下文
const setupResult = setup && setup(instance.props,setupContext);
handleSetupResult(instance,setupResult); // 处理返回值
currentInstance = null;
}else{
finishComponentSetup(instance)
}
}
function createSetupContext(instance){ // 创建setup上下文
return {
attrs:instance.attrs,
slots:instance.slots,
}
}
function handleSetupResult(instance,setupResult){ // 返回值可以是函数
if(isFunction(setupResult)){
instance.render = setupResult
}else if(isObject(setupResult)){
instance.setupState = setupResult
}
finishComponentSetup(instance)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function finishComponentSetup(instance){
const Component = instance.type;
// 没render函数进行模板编译
if(!instance.render){
if(Component.template && !Component.render){
// 模板编译
}
instance.render = Component.render
}
// applyOptions 2.0API兼容处理
// applyOptions(instance,Component);
}
2
3
4
5
6
7
8
9
10
11
12
applyOptions 中兼容vue2.0写法
# 初始化渲染effect
保证组件中数据变化可以重新进行组件的渲染
const mountComponent = (initialVNode, container) => {
// 1.创建组件实例
const instance = (initialVNode.component = createComponentInstance(
initialVNode
))
// 2.给instance赋值
setupComponent(instance)
// 3.给组件增加渲染effect
setupRenderEffect(instance, initialVNode, container);
}
2
3
4
5
6
7
8
9
10
11
export const setupRenderEffect = (instance, initialVNode, container) => {
instance.update = effect(function componentEffect(){
if(!instance.isMounted){
const proxyToUse = instance.proxy; // 实例中的代理属性
const subTree = (instance.subTree = instance.render.call(proxyToUse,proxyToUse));
patch(null,subTree,container); // 渲染子树
initialVNode.el = subTree.el; // 组件的el和子树的el是同一个
instance.isMounted = true; // 组件已经挂载完毕
}else{
console.log('更新逻辑')
}
})
}
2
3
4
5
6
7
8
9
10
11
12
13
render函数中返回的是虚拟节点,例如
const App = {
render : (r) =>h('div', {}, 'hello sy')
}
2
3
# 元素创建流程
# h
方法的实现
// 1. 只有两个参数 类型 + 孩子 / 类型 + 属性
// 2. 三个参数 最后一个不是数组
// 3. 超过三个 多个参数
export function h(type,propsOrChildren,children){
const l = arguments.length;
if(l === 2){
// 是对象不是数组, 只有一个节点
if(isObject(propsOrChildren) && !isArray(propsOrChildren)){
if(isVnode(propsOrChildren)){
return createVNode(type,null,[propsOrChildren]);
}
return createVNode(type,propsOrChildren); // 没有孩子
}else{
return createVNode(type, null, propsOrChildren)
}
}else{
if(l > 3){
children = Array.prototype.slice.call(arguments,2);
}else if(l === 3 && isVnode(children)){
children = [children]
}
return createVNode(type,propsOrChildren,children)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 创建真实ele节点
const mountElement = (vnode, container) => {
// 创建节点保存到vnode中
const { props, shapeFlag, type, children } = vnode
let el = vnode.el = hostCreateElement(type);
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { // 文本直接插入即可
hostSetElementText(el, children);
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(children, el)
}
if (props) { // 处理属性
for (const key in props) {
hostPatchProp(el, key, null, props[key])
}
}
hostInsert(el, container)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
对子节点进行处理
const mountChildren = (children, container) => {
for (let i = 0; i < children.length; i++) {
const child = normalizeVNode(children[i]);
patch(null, child, container)
}
}
2
3
4
5
6
export const Text = Symbol('Text')
export function normalizeVNode(child) { // 对节点进行标识
if(isObject(child)){
return child
}
return createVNode(Text,null,String(child));
}
2
3
4
5
6
7
const processText = (n1,n2,container) =>{
if(n1 == null){ // 创建文本插入到容器中
hostInsert(n2.el = hostCreateText(n2.children),container)
}
}
2
3
4
5
# 文本创建流程
const {
insert: hostInsert,
remove: hostRemove,
patchProp: hostPatchProp,
createElement: hostCreateElement,
createText: hostCreateText,
createComment: hostCreateComment,
setText: hostSetText,
setElementText: hostSetElementText,
nextSibling: hostNextSibling,
} = rendererOptions;
// -----------------文本处理-----------------
const processText = (n1, n2, container) => {
if (n1 == null) {
hostInsert((n2.el = hostCreateText(n2.children)), container);
}
};
// -----------------文本处理----end-------------
const isSameVNodeType = (n1, n2) => {
return n1.type === n2.type && n1.key === n2.key;
};
const unmount = (n1) => {
hostRemove(n1.el); // 如果是组件 调用的组件的生命周期等
};
// 开始patch渲染入口
const patch = (n1, n2, container, anchor = null) => {
// 针对不同类型 做初始化操作
const { shapeFlag, type } = n2;
if (n1 && !isSameVNodeType(n1, n2)) {
// 把以前的删掉 换成n2
anchor = hostNextSibling(n1.el);
unmount(n1);
n1 = null; // 重新渲染n2 对应的内容
}
switch (type) {
case Text:
processText(n1, n2, container); // 处理文本类型
break;
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(n1, n2, container, anchor); // 处理元素类型
} else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
processComponent(n1, n2, container); // 处理组件类型
}
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 生命周期原理
# 实现原理
定义生命周期类型
export const enum LifecycleHooks {
BEFORE_MOUNT = 'bm',
MOUNTED = 'm',
BEFORE_UPDATE = 'bu',
UPDATED = 'u',
}
2
3
4
5
6
apiLifecycle.ts:将对应的生命周期保存在实例上
import { currentInstance, LifecycleHooks, setCurrentInstance } from "./component"
export function injectHook(type, hook, target) {
if (target) {
const hooks = target[type] || (target[type] = []); // 将生命周期保存在实例上
const wrappedHook = () =>{
setCurrentInstance(target); // 当生命周期调用时 保证currentInstance是正确的
hook.call(target);
setCurrentInstance(null);
}
hooks.push(wrappedHook);
}
}
export const createHook = (lifecycle) => (hook, target = currentInstance) => {
injectHook(lifecycle, hook, target)
}
// N个生命周期
export const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);
export const onMounted = createHook(LifecycleHooks.MOUNTED);
export const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);
export const onUpdated = createHook(LifecycleHooks.UPDATED);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 渲染调用
shared.ts 获取每个fn执行
export const invokeArrayFns = (fns, arg?: any) => {
for (let i = 0; i < fns.length; i++) {
fns[i](arg)
}
}
2
3
4
5
instance.update = effect(function componentEffect() {
if (!instance.isMounted) {
const {bm, m , parent} = instance;
if(bm){ // beforeMount
invokeArrayFns(bm);
}
const proxyToUse = instance.proxy;
const subTree = (instance.subTree = instance.render.call(proxyToUse, proxyToUse));
patch(null, subTree, container);
initialVNode.el = subTree.el; // 组件的el和子树的el是同一个
instance.isMounted = true; // 组件已经挂载完毕
if(m){ // mounted
queuePostFlushCb(m)
}
} else {
const prevTree = instance.subTree;
const proxyToUse = instance.proxy;
const {bu,u} = instance;
if(bu){ // beforeUpdate
invokeArrayFns(bu);
}
const nextTree = instance.render.call(proxyToUse, proxyToUse); // 在来一次
if(u){ // updated
queuePostFlushCb(u);
}
instance.subTree = nextTree
patch(prevTree, nextTree, container);
}
}, {
scheduler: queueJob
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
在完成渲染后执行生命周期钩子
export function queuePostFlushCb(cb) { // cb 可能是一个数组
queueCb(cb, pendingPostFlushCbs)
}
function queueCb(cb, pendingQueue) {
if(!isArray(cb)){
pendingQueue.push(cb);
}else{
pendingQueue.push(...cb);
}
queueFlush();
}
2
3
4
5
6
7
8
9
10
11
# 编译原理
在
compiler-dom
模块目录下;
# 模板调试
Vue中对
template
属性会编译成render
方法。vue-next
源码可以直接运行命令实现在线调试。打开网址:本地地址 (opens new window)
npm run dev-compiler
# 模板编译步骤
export function baseCompile(template) {
// 1.生成ast语法树
const ast = baseParse(template);
// 2.转化ast语法树
transform(ast)
// 3.根据ast生成代码
return generate(ast);
}
2
3
4
5
6
7
8
# 生成AST
语法树
创建解析上下文,开始进行解析
function baseParse(content) {
// 创建解析上下文,在整个解析过程中会修改对应信息
const context = createParserContext(content);
// 解析孩子代码
return parseChildren(context);
}
2
3
4
5
6
function createParserContext(content) {
return {
column: 1, // 列数
line: 1, // 行数
offset: 0, // 偏移字符数
originalSource: content, // 原文本不会变
source: content // 解析的文本 -> 不停的减少
}
}
2
3
4
5
6
7
8
9
对不同内容类型进行解析
解析节点的类型有:
export const enum NodeTypes {
ROOT,
ElEMENT,
TEXT,
SIMPLE_EXPRESSION = 4,
INTERPOLATION = 5,
ATTRIBUTE = 6,
DIRECTIVE = 7,
COMPOUND_EXPRESSION = 8,
TEXT_CALL = 12,
VNODE_CALL = 13,
JS_CALL_EXPRESSION = 17
}
2
3
4
5
6
7
8
9
10
11
12
13
function isEnd(context) {
const s = context.source; // 字符串解析完毕就结束
return !s;
}
function parseChildren(context){
const nodes = [];
while (!isEnd(context)) {
let node; // 解析节点
const s = context.source;
if (s.startsWith('{{')) {
node = parseInterpolation(context);// 解析双括号
} else if (s[0] == '<') {
node = parseElement(context); // 解析标签
} else {
node = parseText(context);// 文本
}
nodes.push(node);
}
return nodes
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 1.解析文本
文本可能是
我是文本
、
我是文本
function parseText(context) {
const endTokens = ['<', '{{']; // 当遇到 < 或者 {{ 说明文本结束
let endIndex = context.source.length;
for (let i = 0; i < endTokens.length; i++) {
const index = context.source.indexOf(endTokens[i], 1);
if (index !== -1 && endIndex > index) { // 找到离着最近的 < 或者 {{
endIndex = index
}
}
const start = getCursor(context); // 开始
const content = parseTextData(context, endIndex); // 获取文本内容
return {
type: NodeTypes.TEXT, // 文本
content,
loc: getSelection(context, start)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
用于获取当前解析的位置
function getCursor(context) { // 获取当前位置信息
const { column, line, offset } = context;
return { column, line, offset };
}
2
3
4
function parseTextData(context, endIndex) { // 截取文本部分,并删除文本
const rawText = context.source.slice(0, endIndex);
advanceBy(context, endIndex);
return rawText;
}
2
3
4
5
将解析的部分移除掉,并且更新上下文信息
function advanceBy(context, index) {
let s = context.source
advancePositionWithMutation(context, s, index)
context.source = s.slice(index); // 将文本部分移除掉
}
const advancePositionWithMutation = (context, source, index) => {
let linesCount = 0
let lastNewLinePos = -1;
for (let i = 0; i < index; i++) {
if (source.charCodeAt(i) == 10) {
linesCount++; // 计算走了多少行
lastNewLinePos = i; // 记录换行的首个位置
}
}
context.offset += index; // 更新偏移量
context.line += linesCount; // 更新行号
context.column = lastNewLinePos === -1 ? context.column + index : index - lastNewLinePos
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
解析结果:
const {baseCompile} = VueCompilerDOM;
console.log(baseCompile(`syjg`))
2
content: "syjg"
loc:
end: {column: 5, line: 1, offset: 4}
source: "syjg"
start: {column: 1, line: 1, offset: 0}
type: 2
2
3
4
5
6
# 2.解析表达式
获取花括号中的内容
function parseInterpolation(context) {
const closeIndex = context.source.indexOf('}}', '{{');
const start = getCursor(context);
advanceBy(context, 2);
const innerStart = getCursor(context);// 获取内部的开始
const innerEnd = getCursor(context);
const rawContentLength = closeIndex - 2; // 内容结束位置
// 去空格前的内容 和 去空格后的内容
const preTrimContent = parseTextData(context, rawContentLength);
const content = preTrimContent.trim();
const startOffset = preTrimContent.indexOf(content);
if (startOffset > 0) { // 根据标签开始位置修改innerStart
advancePositionWithMutation(innerStart, preTrimContent, startOffset)
}
const endOffset = content.length + startOffset;
// 根据标签结束位置修改innerStart
advancePositionWithMutation(innerEnd, preTrimContent, endOffset);
advanceBy(context, 2);
return {
type: NodeTypes.INTERPOLATION,
content: {
type: NodeTypes.SIMPLE_EXPRESSION,
isStatic: false,
loc: getSelection(context, innerStart, innerEnd)
},
loc: getSelection(context, start)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
解析结果:
const { baseCompile } = VueCompilerDOM;
console.log(baseCompile(`{{ name }}`))
2
content:
isStatic: false
loc: {start: {…}, end: {…}, source: "name"}
type: 4
loc:
end: {column: 13, line: 1, offset: 12}
source: "{{ name }}"
start: {column: 1, line: 1, offset: 0}
type: 5
2
3
4
5
6
7
8
9
# 3.解析元素
获取标签名属性
function isEnd(context) {
const s = context.source;
if (s.startsWith('</')) { // 遇到闭合标签
return true;
}
return !s;
}
}
function parseElement(context) {
const element: any = parseTag(context);
const children = parseChildren(context); // 11.解析儿子 最后
if (context.source.startsWith('</')) {
parseTag(context)
}
element.children = children
element.loc = getSelection(context,element.loc.start)
return element
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function advanceSpaces(context) {
const match = /^[\t\r\n\f ]+/.exec(context.source)
if (match) {
advanceBy(context, match[0].length)
}
}
function parseTag(context) {
const start = getCursor(context);
const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source)!
const tag = match[1];
advanceBy(context, match[0].length);
advanceSpaces(context);
let isSelfClosing = context.source.startsWith('/>');
advanceBy(context,isSelfClosing?2:1);
return {
type:NodeTypes.ElEMENT,
tag,
isSelfClosing,
loc:getSelection(context,start)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
解析结果:
const { baseCompile } = VueCompilerDOM;
console.log(baseCompile(`<div><p></p></div>`))
2
children: Array(1)
children: []
isSelfClosing: false
loc: {start: {…}, end: {…}, source: "<p></p>"}
tag: "p"
type: 1
isSelfClosing: false
loc: {start: {…}, end: {…}, source: "<div><p></p></div>"}
tag: "div"
type: 1
2
3
4
5
6
7
8
9
10
解析属性
在开始标签解析完毕后解析属性
function parseTag(context) {
const start = getCursor(context); // 获取开始位置
const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source)!
const tag = match[1];
advanceBy(context, match[0].length);
advanceSpaces(context)
let props = parseAttributes(context);
// ...
return {
type: NodeTypes.ElEMENT,
tag,
isSelfClosing,
loc: getSelection(context, start),
props
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function parseAttributes(context) {
const props: any = [];
while (context.source.length > 0 && !startsWith(context.source, '>')) {
const attr = parseAttribute(context)
props.push(attr);
advanceSpaces(context); // 解析一个去空格一个
}
return props
}
2
3
4
5
6
7
8
9
function parseAttribute(context) {
const start = getCursor(context);
const match = /^[^\t\r\n\f />][^\t\r\n\f />=]*/.exec(context.source)!
const name = match[0];
advanceBy(context, name.length);
let value
if (/^[\t\r\n\f ]*=/.test(context.source)) {
advanceSpaces(context);
advanceBy(context, 1);
advanceSpaces(context);
value = parseAttributeValue(context);
}
const loc = getSelection(context, start)
if (/^(:|@)/.test(name)) { // :xxx @click
let dirName = name.slice(1)
return {
type: NodeTypes.DIRECTIVE,
name: dirName,
exp: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: value.content,
isStatic: false,
loc: value.loc
},
loc
}
}
return {
type: NodeTypes.ATTRIBUTE,
name,
value: {
type: NodeTypes.TEXT,
content: value.content,
loc: value.loc
},
loc
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function parseAttributeValue(context) {
const start = getCursor(context);
const quote = context.source[0];
let content
const isQuoteed = quote === '"' || quote === "'"; // 解析引号中间的值
if (isQuoteed) {
advanceBy(context, 1);
const endIndex = context.source.indexOf(quote);
content = parseTextData(context, endIndex);
advanceBy(context, 1);
}
return { content, loc: getSelection(context, start) }
}
2
3
4
5
6
7
8
9
10
11
12
13
对文本节点稍做处理
function parseChildren(context) {
const nodes: any = [];
while (!isEnd(context)) {
//....
}
for(let i = 0 ;i < nodes.length; i++){
const node = nodes[i];
if(node.type == NodeTypes.TEXT){ // 如果是文本 删除空白文本,其他的空格变为一个
if(!/[^\t\r\n\f ]/.test(node.content)){
nodes[i] = null
}else{
node.content = node.content.replace(/[\t\r\n\f ]+/g, ' ')
}
}
}
return nodes.filter(Boolean)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 4.处理多个根节点
export function baseParse(content) {
// 创建解析上下文,在整个解析过程中会修改对应信息
const context = createParserContext(content);
// 解析代码
const start = getCursor(context);
return createRoot(
parseChildren(context),
getSelection(context,start)
)
}
2
3
4
5
6
7
8
9
10
将解析出的节点,再次进行包裹
ast.ts
export function createRoot(children,loc){
return {
type:NodeTypes.ROOT,
children,
loc
}
}
2
3
4
5
6
7
# transform实现
定义转化标识
export const CREATE_VNODE = Symbol('createVnode');
export const TO_DISPALY_STRING = Symbol('toDisplayString');
export const OPEN_BLOCK = Symbol('openBlock');
export const CREATE_BLOCK = Symbol('createBlock')
export const FRAGMENT = Symbol('Fragment');
export const CREATE_TEXT = Symbol('createTextVNode');
export const helperNameMap: any = {
[FRAGMENT]: `Fragment`,
[OPEN_BLOCK]: `openBlock`,
[CREATE_BLOCK]: `createBlock`,
[CREATE_VNODE]: `createVNode`,
[TO_DISPALY_STRING]: "toDisplayString",
[CREATE_TEXT]: "createTextVNode"
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
对AST语法树进行转化,主要是对AST语法树进行优化操作
export function baseCompile(template) {
// 1.生成ast语法树
const ast = baseParse(template);
// 得到对应的转化方法 元素转化、文本转化... 还有指令转化都应该在这里实现
const nodeTransforms = getBaseTransformPreset();
transform(ast, { nodeTransform })
}
2
3
4
5
6
7
function transformElement(node,context) {
// 转化标签 需要处理他的子节点,所以需要等待子节点遍历完成在处理
if(!(node.type === 1)){ // 不是元素就不必执行了
return;
}
console.log('转化元素',node)
}
function transformText(node,context) {
// 处理文本 需要处理他的同级 表达式/文本,所以需要处理完同级后在处理
if (node.type === NodeTypes.ROOT || node.type === NodeTypes.ElEMENT) {
console.log('内部可能包含文本', node);
}
}
function getBaseTransformPreset() {
return [
transformElement,
transformText
]// ...指令转化
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
开始进行转化,会先创建转化上下文,之后遍历ast树
function createTransformContext(root, { nodeTransforms }) {
const context = {
root, // 转化的完整ast
currentNode: root, // 当前转化的节点
nodeTransforms, // 转化方法
helpers: new Set(), // 收集导入的包
helper(name) {
context.helpers.add(name);
return name;
}
}
return context;
}
2
3
4
5
6
7
8
9
10
11
12
13
function transform(root, options) {
const context = createTransformContext(root, options);
traverseNode(root, context)
}
2
3
4
深度遍历节点,调用transform函数
function traverseNode(node, context) { // 遍历树
context.currentNode = node;
const { nodeTransforms } = context;
for (let i = 0; i < nodeTransforms.length; i++) {
nodeTransforms[i](node, context);
}
switch (node.type) {
case NodeTypes.ROOT:
case NodeTypes.ElEMENT:
traverseChildren(node, context); // 递归遍历子节点
break;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
function traverseChildren(parent, context) {
for (let i = 0; i < parent.children.length; i++) {
const child = parent.children[i];
traverseNode(child, context); // 遍历节点
}
}
2
3
4
5
6
# 1.退出函数
返回一个函数等递归完成后在执行
function transformElement(node,context) {
// 转化标签 需要处理他的子节点,所以需要等待子节点遍历完成在处理
if(!(node.type === 1)){ // 不是元素就不必执行了
return;
}
return ()=>{
console.log('转化元素',node)
}
}
function transformText(node,context) {
// 处理文本 需要处理他的同级 表达式/文本,所以需要处理完同级后在处理
if (node.type === NodeTypes.ROOT || node.type === NodeTypes.ElEMENT) {
return ()=>{
console.log('内部可能包含文本', node);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function traverseNode(node, context) { // 遍历树
context.currentNode = node;
const { nodeTransforms } = context;
const exitFns = [];
for (let i = 0; i < nodeTransforms.length; i++) {
const onExit = nodeTransforms[i](node, context);
if (onExit) exitFns.push(onExit)
}
switch (node.type) {
case NodeTypes.ROOT:
case NodeTypes.ElEMENT:
traverseChildren(node, context);
break;
}
let i = exitFns.length;
context.currentNode = node; // 保证退出方法的context是正确的
while (i--) {
exitFns[i]()
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 2.文本转化
export const enum PatchFlags {
TEXT = 1,
CLASS = 1 << 1,
STYLE = 1 << 2,
PROPS = 1 << 3,
FULL_PROPS = 1 << 4,
HYDRATE_EVENTS = 1 << 5,
STABLE_FRAGMENT = 1 << 6,
KEYED_FRAGMENT = 1 << 7,
UNKEYED_FRAGMENT = 1 << 8,
NEED_PATCH = 1 << 9,
DYNAMIC_SLOTS = 1 << 10,
DEV_ROOT_FRAGMENT = 1 << 11,
HOISTED = -1,
BAIL = -2
}
function isText(node) {
return node.type == NodeTypes.INTERPOLATION || node.type == NodeTypes.TEXT;
}
export function createCallExpression(callee,args) {
return {
type: NodeTypes.JS_CALL_EXPRESSION,
callee,
arguments: args
}
}
function transformText(node, context) { // 转化文本 核心就是相邻的合并
if (node.type === NodeTypes.ROOT || node.type === NodeTypes.ElEMENT) {
return () => {
// 遍历的是元素,儿子可能是文本。 这里就对标签内的儿子做处理
let hasText = false;
const children = node.children;
let currentContainer = undefined // 合并儿子
for (let i = 0; i < children.length; i++) {
let child = children[i];
if (isText(child)) {
hasText = true;
for (let j = i + 1; j < children.length; j++) {
const next = children[j];
if(isText(next)){
if(!currentContainer){
currentContainer = children[i] = { // 合并表达式
type:NodeTypes.COMPOUND_EXPRESSION,
loc:child.loc,
children:[child]
}
}
currentContainer.children.push(` + `,next);
children.splice(j,1);
j--;
}else{
currentContainer = undefined;
break;
}
}
}
}
if (!hasText || children.length == 1) { // 一个元素不用管,可以执行innerHTML
return
}
for (let i = 0; i < children.length; i++) {
const child = children[i]
if (isText(child) || child.type === NodeTypes.COMPOUND_EXPRESSION) {
const callArgs = []
callArgs.push(child)
if (child.type !== NodeTypes.TEXT) {
callArgs.push(PatchFlags.TEXT + '')
}
children[i] = {
type: NodeTypes.TEXT_CALL,
content: child,
loc: child.loc,
codegenNode: createCallExpression(
context.helper(CREATE_TEXT),
callArgs
)
}
}
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
switch (node.type) {
case NodeTypes.ROOT:
case NodeTypes.ElEMENT:
traverseChildren(node, context);
case NodeTypes.INTERPOLATION: // 给表达式新增导入方法
context.helper(TO_DISPALY_STRING);
break;
}
2
3
4
5
6
7
8
# 3.元素转化
function createVNodeCall(context, tag, props, children, patchFlag) {
context.helper(CREATE_VNODE);
return {
type: NodeTypes.VNODE_CALL,
tag,
props,
children,
patchFlag
}
}
function transformElement(node, context) {
if (!(node.type === 1)) {
return;
}
return () => { // 对元素的处理
const { tag, children } = node;
const vnodeTag = `"${tag}"`; // 标签名
let vnodeProps;
let vnodeChildren;
let vnodePatchFlag;
let patchFlag = 0;
if (node.children.length > 0) {
if (node.children.length === 1) {
const child = node.children[0];
const type = child.type;
const hasDynamicTextChild = type === NodeTypes.INTERPOLATION || type == NodeTypes.COMPOUND_EXPRESSION;
if (hasDynamicTextChild) {
patchFlag |= PatchFlags.TEXT;
}
vnodeChildren = child; // 一个儿子去掉数组
} else {
vnodeChildren = children;
}
}
if (patchFlag !== 0) {
vnodePatchFlag = String(patchFlag);
}
node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren, vnodePatchFlag);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 4.根元素的转化
function createRootCodegen(root, context) {
const { helper } = context
const { children } = root
if (children.length == 1) {
const child = children[0];
// 就一个元素
const codegenNode = child.codegenNode;
codegenNode.isBlock = true
helper(OPEN_BLOCK)
helper(CREATE_BLOCK);
root.codegenNode = codegenNode; // 单个节点就转化成一个block
} else if (children.length > 1) { // 增加fragment
root.codegenNode = createVNodeCall(
context,
helper(FRAGMENT),
undefined,
root.children,
PatchFlags.STABLE_FRAGMENT
)
}
}
export function transform(root, options) {
const context = createTransformContext(root, options);
traverseNode(root, context);
createRootCodegen(root, context)
root.helpers = [...context.helpers]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# codegen实现
export const helperNameMap: any = {
[FRAGMENT]: `Fragment`,
[OPEN_BLOCK]: `openBlock`,
[CREATE_BLOCK]: `createBlock`,
[CREATE_VNODE]: `createVNode`,
[TO_DISPALY_STRING]: "toDisplayString",
[CREATE_TEXT]: "createTextVNode"
}
2
3
4
5
6
7
8
用于做生成代码时产生的方法名
# 1.生成上下文
function createCodegenContext(ast) {
function newline(n) {
context.push('\n' + ` `.repeat(n))
}
const context = {
code: ``, // 拼接出的代码
indentLevel: 0, // 缩进
helper(key){
return `${helperNameMap[key]}`;
},
push(code) {
context.code += code;
},
newline() { // 新行
newline(context.indentLevel)
},
indent() { // 新行 + 2空格
newline(++context.indentLevel);
},
deindent() {// 缩进 2空格
newline(--context.indentLevel);
}
}
return context;
}
function generate(ast) {
const context = createCodegenContext(ast); // 生成代码时所需要的上下文
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 2.生成函数添加with
function generate(ast) {
const context = createCodegenContext(ast); // 生成代码时所需要的上下文
const { indent, deindent, push, newline } = context;
push(`function render(_ctx){`);
indent();
push(`with(_ctx){`);
indent();
push('// todo import..');
newline()
push(`return `);
push(`// todo vnode`)
deindent();
push(`}`);
deindent();
push('}');
return context.code
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
生成导入语句
push(`const {${ast.helpers.map(s => `${helperNameMap[s]}`).join(',')}} = _Vue`);
生成return语句
genNode(ast.codegenNode, context)
# 3.对不同类型做转化
function genNode(node, context) {
if (isString(node)) {
context.push(node)
return
}
if (isSymbol(node)) {
context.push(context.helper(node))
return
}
switch (node.type) {
case NodeTypes.VNODE_CALL:
break;
case NodeTypes.ElEMENT:
break;
case NodeTypes.TEXT:
break;
case NodeTypes.INTERPOLATION:
break;
case NodeTypes.COMPOUND_EXPRESSION:
break;
case NodeTypes.SIMPLE_EXPRESSION:
break;
case NodeTypes.TEXT_CALL:
break
case NodeTypes.JS_CALL_EXPRESSION:
break
default:
break;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 转化方式
# 1.对block节点转化
case NodeTypes.VNODE_CALL:
// 1.最外层元素
genVNodeCall(node, context)
2
3
function genVNodeCall(node, context) {
const { push, helper } = context
const { tag, props, children, patchFlag, isBlock } = node;
if (isBlock) {
push(`(${helper(OPEN_BLOCK)}(),`)
}
push(helper(isBlock ? CREATE_BLOCK : CREATE_VNODE) + '(', node)
genNodeList([tag, props, children, patchFlag], context)
push(`)`);
if (isBlock) {
push(`)`)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
function genNodeList(nodes, context) {
const { push } = context
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (isString(node)) {
push(node);
} else if (node == null) {
push(`null`);
} else if (isArray(node)) { // 是数组就循环
genNodeListAsArray(node, context)
} else { // 否则genNode
genNode(node, context)
}
if (i < nodes.length - 1) {
push(',')
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function genNodeListAsArray(nodes, context) {
context.push(`[`)
context.indent();
console.log(nodes)
genNodeList(nodes, context);
context.deindent();
context.push(']')
}
2
3
4
5
6
7
8
# 2.对元素节点转化
case NodeTypes.ElEMENT:
genNode(node.codegenNode, context)
2
# 3.对文本节点转化
case NodeTypes.TEXT:
genText(node, context);
2
function genText(node,context) {
context.push(JSON.stringify(node.content), node)
}
2
3
# 4.对表达式的转化
case NodeTypes.INTERPOLATION:
genInterpolation(node, context)
2
function genInterpolation(node, context) {
const { push, helper } = context
push(`${helper(TO_DISPALY_STRING)}(`)
genNode(node.content, context)
push(`)`)
}
2
3
4
5
6
# 5.简单表达式
case NodeTypes.SIMPLE_EXPRESSION:
genExpression(node, context)
2
function genExpression(node, context) {
const { content, isStatic } = node
context.push(content)
}
2
3
4
# 6.复合表达式
case NodeTypes.COMPOUND_EXPRESSION:
genCompoundExpression(node, context)
2
function genCompoundExpression(
node,
context
) {
for (let i = 0; i < node.children!.length; i++) {
const child = node.children![i]
if (isString(child)) {
context.push(child)
} else {
genNode(child, context)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 7.文本函数处理
case NodeTypes.TEXT_CALL:
genNode(node.codegenNode, context)
break
case NodeTypes.JS_CALL_EXPRESSION:
genCallExpression(node, context)
break
2
3
4
5
6
function genCallExpression(node, context) {
const { push, helper } = context
const callee = isString(node.callee) ? node.callee : helper(node.callee)
push(callee + `(`, node)
genNodeList(node.arguments, context)
push(`)`)
}
2
3
4
5
6
7
# 异步更新原理
# watchAPI
watchAPI 的核心就是监控值的变化,值发生变化后调用对应的回调函数;
在apiWatch.js
中处理;
# 同步watch
使用示范:
const state = reactive({ name: 'sy' })
watch(() => state.name, (newValue, oldValue) => {
console.log(newValue, oldValue)
}, { flush: 'sync' }); // 同步watcher
setTimeout(() => {
state.name = 'ysz'
state.name = 'sy'
}, 1000);
2
3
4
5
6
7
8
watchAPI根据传入的参数不同,有不同的调用方式
export function watch(source, cb, options:any = {}) {
dowatch(source, cb, options)
}
function dowatch(source, cb, {flush,immediate}) {
let getter = () => source.call(currentInstance); // 保证函数中的this 是当前实例
let oldValue;
const job = () => {
if(cb){
const newValue = runner(); // 获取新值
if(hasChanged(newValue,oldValue)){ // 如果有变化,调用对应的callback
cb(newValue,oldValue);
oldValue = newValue; // 更新值
}
}
}
let scheduler;
if(flush == 'sync') {
scheduler = job
}else if(flush === 'post'){
}else {
// flush === 'pre'
}
const runner = effect(getter, {
lazy: true,
scheduler
})
if(cb){
if(immediate){
job(); // 立即让cb执行
}else{
oldValue = runner(); // 仅执行不调用 cb
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 异步watch
使用示范:
多次进行更改操作,最终仅仅执行一次
const state = reactive({ name: 'sy' })
watch(() => state.name, (newValue, oldValue) => {
console.log(newValue, oldValue); // xxx sy
});
setTimeout(() => {
state.name = 'ysz'
state.name = 'xxx'
}, 1000);
2
3
4
5
6
7
8
根据参数不同,将任务放到不同的队列中
let pendingPreFlushCbs = [] // preCallback
let pendingPostFlushCbs = [] // postCallback
export function queuePreFlushCb(cb) {
queueCb(cb, pendingPreFlushCbs)
}
export function queuePostFlushCb(cb) {
queueCb(cb, pendingPostFlushCbs)
}
function queueCb(cb, pendingQueue) {
pendingQueue.push(cb);
queueFlush();
}
let isFlushPending = false
function queueFlush() {
if (!isFlushPending) { //保证queueFlush方法只能调用一次
isFlushPending = true;
Promise.resolve().then(flushJobs)
}
}
function flushJobs() {
isFlushPending = false;
flushPreFlushCbs(); // 刷新队列
flushPostFlushCbs();
}
function flushPreFlushCbs() {
if(pendingPreFlushCbs.length) {
const deduped: any = [...new Set(pendingPreFlushCbs)];
pendingPreFlushCbs.length = 0;
console.log(deduped)
for (let i = 0; i < deduped.length; i++) {
deduped[i]();
}
flushPreFlushCbs(); // 递归直到用尽
}
}
function flushPostFlushCbs() {
if(pendingPostFlushCbs.length) {
const deduped: any = [...new Set(pendingPostFlushCbs)];
pendingPostFlushCbs.length = 0;
for (let i = 0; i < deduped.length; i++) {
deduped[i]();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# watchEffect
使用示范:
watchEffect是没有cb的watch,当数据变化后会重新执行source函数
const state = reactive({ name: 'sy' })
watchEffect(()=>console.log(state.name));
state.name = 'ysz'
2
3
watchEffect实现
export function watchEffect(source,options){
dowatch(source, null, options)
}
2
3
function dowatch(source, cb, {flush,immediate}) {
const job = () => {
if(cb){
// ....
}else{ // watchEffect 不需要新旧对比
runner()
}
}
if(cb){
// ...
}else{ // watchEffect 默认会执行一次
runner();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
导出:
// export { computed } from './apiComputed'
export { watch, watchEffect } from './apiWatch'
2
# 组件异步更新原理
使用示范:
const App = {
setup() {
const state = reactive({ name: 'sy' });
setTimeout(() => { // 多次更新状态
state.name = 'ysz';
state.name = 'sy';
state.name = 'zry';
}, 1000);
return {
state
}
},
render: (r) => {
console.log('render~~~'); // 造成多次渲染
return h('div', r.state.name)
}
}
createApp(App).mount('#app');
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
给组件更新提供scheduler函数
const setupRenderEffect = (instance, initialVNode, container) => {
instance.update = effect(function componentEffect() {
// ....
}, {
scheduler: queueJob
})
}
2
3
4
5
6
7
queueJob的原理也是将渲染函数维护到队列中
let queue: any = []
export function queueJob(job) {
if (!queue.includes(job)) {
queue.push(job);
queueFlush();
}
}
2
3
4
5
6
7
function flushJobs() {
isFlushPending = false;
flushPreFlushCbs(); // 渲染之前
// 清空时 我们需要根据调用的顺序依次刷新 , 保证先刷新父在刷新子
queue.sort((a, b) => a.id - b.id);
for (let i = 0; i < queue.length; i++) {
const job = queue[i];
job();
}
queue.length = 0;
flushPostFlushCbs(); // 渲染之后
}
function flushPreFlushCbs() {
if (pendingPreFlushCbs.length) {
const deduped: any = [...new Set(pendingPreFlushCbs)];
pendingPreFlushCbs.length = 0;
// console.log(deduped);
for (let i = 0; i < deduped.length; i++) {
deduped[i]();
}
flushPreFlushCbs(); // 递归直到用尽
}
}
function flushPostFlushCbs() {
if (pendingPostFlushCbs.length) {
const deduped: any = [...new Set(pendingPostFlushCbs)];
pendingPostFlushCbs.length = 0;
for (let i = 0; i < deduped.length; i++) {
deduped[i]();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# queueFlush实现
nextTick本质原理就是个promise(微任务),这里会将effect暂存起来并进行去重之后执行。
let isFlushPending = false; // 是否正在等待刷新
let isFlushing = false; // 是否正在刷新
const p = Promise.resolve();
function nextTick(fn) {
return fn ? p.then(fn) : p
}
function flushPostFlushCbs(){
if(postFlushCbs.length){ // 队列有值进行队列刷新
const cbs = [...new Set(postFlushCbs)];
postFlushCbs.length = 0;
for(let i = 0; i < cbs.length;i++){
cbs[i]();
}
}
}
function flushJobs() {
isFlushPending = false; // 开始执行任务
isFlushing = true; // 正在刷新
flushPostFlushCbs(); // 刷新队列
isFlushing = false; // 刷新完毕
}
function queueFlush() {
if (!isFlushPending && !isFlushing) {
isFlushPending = true;
nextTick(flushJobs); // 稍后刷新任务队列
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# effect/watch/watchEffect比较
# effect特点
每次更新状态,都会重新运行effect。如果要是effect中包含渲染逻辑,可能会导致多次更新视图。
import { effect, reactive } from './reactivity';
let state = reactive({ name: 'samy', age: 11 })
effect(() => {
console.log(state.name);
})
state.name = 'samy';
state.name = 'ysz';
state.name = 'jg';
2
3
4
5
6
7
8
# watch特点
watch也是effect;watch 函数用来侦听特定的数据源,并在回调函数中执行副作用。默认情况是懒执行的,也就是说仅在侦听的源数据变更时才执行回调。
<template>
<div class="home">
<h1>当前数字为:{{num}}</h1>
<button @click="num++">点击数字加一</button>
</div>
</template>
<script>
import {ref,watch} from 'vue'
export default {
name: 'Home',
setup(){
let num=ref('0')
watch(num,(newValue,oldValue)=>{
console.log(`当前数字增加了,${newValue},${oldValue}`)
})
//既然监听的是数组,那么得到的newValue和oldValue也就是数组,那么数组中的第一个就是你监视的第一个参
watch([num,msg],(newValue,oldValue)=>{
console.log('当前改变了',newValue,oldValue)
})
return {
num
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# watchEffect特点
watchEffect也是effect,只是自定义了
scheduler
函数
import { effect } from "./reactivity";
export function watchEffect(effect, options) {
return doWatch(effect, null, options);
}
let postFlushCbs = [];
function queuePostFlushCb(cb){
postFlushCbs(cb); // 将effect放到数组中进行刷新
queueFlush();
}
function doWatch(source, cb, options) { // 做watch
let getter;
if (isFunction(source)) {
getter = () => source();
}
let scheduler = (job) => queuePostFlushCb(job);
const runner = effect(getter,{ // 创建一个effect
lazy:true,
computed: true,
scheduler // 自定义scheduler
})
runner();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 核心原理-简写总体【要点】
# 虚拟DOM转真实DOM
先创建一个虚拟节点对象,调用render方法将虚拟节点转化为真实节点。
let { render } = Vue
const state = { count: 0 };
const vnode = {
tag: 'div',
props: {
style: { color: 'red' }
},
children: [{
tag: 'p',
props: null,
children: `vue@3- 计数器 ${state.count}`
}, {
tag: 'button',
props: {
onClick: () => alert(state.count)
},
children: '点我啊'
}]
}
render(vnode, app);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 实现DOM操作方法
export const nodeOps = {
insert: (child, parent, anchor) => {
if (anchor) {
parent.insertBefore(child, anchor);
} else {
parent.appendChild(child);
}
},
remove: (child) => {
const parent = child.parentNode;
if (parent) {
parent.removeChild(child);
}
},
createElement: (tag) => document.createElement(tag),
setElementText: (el, text) => el.textContent = text
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 将虚拟节点转化为真实节点
import { nodeOps } from './runtime-dom';
export function render(vnode, container) {
// 渲染分为两种 一种是初始化 一种是是diff
patch(null, vnode, container);
}
function patch(n1, n2, container) {
if (typeof n2.tag == 'string') {
// 将虚拟节点挂载到对应的容器中
mountElement(n2, container);
}
}
function mountElement(vnode, container) {
const { tag, props, children } = vnode;
let el = nodeOps.createElement(tag);
if (typeof children === 'string') {
nodeOps.setElementText(el, children);
} else if (Array.isArray(children)) {
mountChildren(children, el);
}
nodeOps.insert(el, container, null);
}
// 循环挂载子元素
function mountChildren(children, container) {
for (let i = 0; i < children.length; i++) {
const child = children[i];
patch(null, child, container);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 处理DOM中的属性
const onRe = /^on[^a-z]/;
export const nodeOps = {
// ...
hostPatchProps: (el, key, value) => {
const isOn = key => onRe.test(key);
if (isOn(key)) { // 事件添加
const name = key.slice(2).toLowerCase();
el.addEventListener(name, value);
} else {
if (key === 'style') { // 样式处理
for (let key in value) {
el.style[key] = value[key];
}
} else {
el.setAttribute(key, value);
}
}
}
}
function mountElement(vnode, container) {
const { tag, props, children } = vnode;
let el = (vnode.el = nodeOps.createElement(tag));
if (props) {
// 循环所有属性添加属性
for (let key in props) {
nodeOps.hostPatchProps(el, key, props[key]);
}
}
if (typeof children === 'string') {
nodeOps.setElementText(el, children);
} else if (Array.isArray(children)) {
mountChildren(children, el);
}
nodeOps.insert(el, container, null);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 组件的实现
const MyComponent = {
setup() {
return () => ({
tag: 'div',
props: { style: { color: 'blue' } },
children: '我是一个组件' + state.count
})
}
}
2
3
4
5
6
7
8
9
Vue3.x中组件拥有setup方法,当组件渲染前会先调用此方法
const vnode = {
tag: 'div',
props: {
style: { color: 'red' }
},
children: [{
tag: 'p',
props: null,
children: `vue@3- 计数器 ${state.count}`
}, {
tag: 'button',
props: {
onClick: () => alert(state.count)
},
children: '点我啊'
},
{ tag: MyComponent, props: null, children: '' },
{ tag: MyComponent, props: null, children: '' }
]
}
render(vnode, app);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
将组件同时传入children属性中。内部根据tag类型做不同的初始化操作
function patch(n1, n2, container) {
if (typeof n2.tag == 'string') {
// 将虚拟节点挂载到对应的容器中
mountElement(n2, container);
}else if (typeof n2.tag === 'object') {
// 组件的挂载
mountComponent(n2, container);
}
}
function mountComponent(vnode, container) {
const instance = { // 创建元素实例
vnode,
tag: vnode.tag,
render: null, // setup返回的结果
subTree: null, // 子元素
}
const Component = instance.tag;
instance.render = Component.setup(); // 调用setUp方法
instance.subTree = instance.render && instance.render();
patch(null, instance.subTree, container); // 将子树挂载在元素上
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 响应式原理
effect(() => {
const vnode = {
tag: 'div',
props: {
style: { color: 'red' }
},
children: [{
tag: 'p',
props: null,
children: `vue@3- 计数器` + state.count
}, {
tag: 'button',
props: {
onClick: () => state.count++
},
children: '点我啊'
},
{ tag: MyComponent, props: null, children: '' },
{ tag: MyComponent, props: null, children: '' }
]
}
render(vnode, app);
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
注入副作用函数,当数据变化时可以自动重新执行。
let activeEffect;
export function effect(fn) {
activeEffect = fn;
fn();
}
export function reactive(target) {
return new Proxy(target, {
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver);
activeEffect();
return res;
},
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
return res;
}
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
通过proxy代理数据当数据更新时,重新执行effect函数。
依赖收集原理
加入依赖收集功能;
let activeEffect;
export function effect(fn) {
activeEffect = fn;
fn();
activeEffect = null; // 当前effect置为空
}
const targetMap = new WeakMap();
function track(target,key){ // 依赖收集
let depsMap = targetMap.get(target);
if(!depsMap){ // 属性对应依赖关系
targetMap.set(target,(depsMap = new Map()));
}
let deps = depsMap.get(key);
if(!deps){ // 设置set 存放effect
depsMap.set(key,(deps=new Set()));
}
if(activeEffect && !deps.has(activeEffect)){
deps.add(activeEffect);
}
}
function trigger(target,key,val){
const depsMap = targetMap.get(target);
if(!depsMap){
return;
}
const effects = depsMap.get(key);
effects && effects.forEach(effect =>effect());
}
export function reactive(target) {
return new Proxy(target, {
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver);
trigger(target,key,value); // 触发更新
return res;
},
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
track(target,key); // 收集依赖
return res;
}
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 组件级更新
function mountComponent(vnode, container) {
const instance = {
vnode,
tag: vnode.tag,
render: null, // setup返回的结果
subTree: null, // 子元素
}
const Component = instance.tag;
instance.render = Component.setup(); // 调用setUp方法
effect(()=>{
// 给组件添加effect,组件中的属性变化时只执行对应方法
instance.subTree = instance.render && instance.render();
patch(null, instance.subTree, container); // 将子树挂载在元素上
})
}
const MyComponent = {
setup() {
return () => ({
tag: 'div',
props: { style: { color: 'blue' } },
children: [
{
tag: 'h3',
children: '姓名:' + state.name
},
{
tag: 'button',
children: '更新',
props: {
onClick:()=>state.name = 'ysz'
}
}
]
})
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
只更新组件对应区域
# diff算法
详见后面分析
# Reactivity实现-简写【要点】
# 配置Webpack开发环境
安装依赖
yarn add webpack webpack-dev-server webpack-cli html-webpack-plugin @babel/core babel-loader @babel/preset-env -D
webpack.config.js文件编写
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry:'./src/index.js',
output:{
filename:'bundle.js'
},
devtool:'source-map',
module:{
rules:[
{
test:/\.js/,use:'babel-loader',exclude:/node_modules/
}
]
},
plugins:[
new HtmlWebpackPlugin({
template:'./public/index.html'
})
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
配置.babelrc文件
{
"presets": ["@babel/preset-env"]
}
2
3
执行脚本配置
"scripts": {
"build:dev": "webpack --mode development",
"serve": "webpack-dev-server"
}
2
3
4
使用Vue3响应式模块
import { reactive, effect } from '@vue/reactivity'
let state = reactive({
name: 'samy',
age: 11,
});
effect(() => {
console.log(state.name)
});
2
3
4
5
6
7
8
# 目录结构搭建
computed.js
effect.js
index.js
reactive.js
ref.js
这里我们要实现的方法分别用
reactive
、effect
、ref
、computed
方法。在index
文件中统一整合这些方法进行导出
export {computed} from './computed';
export {effect} from './effect';
export {reactive} from './reactive';
export {ref} from './ref';
2
3
4
# reactive实现
import { mutableHandlers } from './baseHandlers'; // 代理相关逻辑
import { isObject } from './util'; // 工具方法
export function reactive(target) {
// 根据不同参数创建不同响应式对象
return createReactiveObject(
target,
mutableHandlers
)
}
function createReactiveObject(target, baseHandler) {
if (!isObject(target)) {
return target;
}
const observed = new Proxy(target, baseHandler);
return observed
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
baseHandlers
import { isObject, hasOwn, hasChanged } from "./util";
import { reactive } from "./reactive";
const get = createGetter();
const set = createSetter();
function createGetter() {
return function get(target, key, receiver) {
// 对获取的值进行放射
const res = Reflect.get(target, key, receiver);
console.log('属性获取',key)
if (isObject(res)) { // 如果获取的值是对象类型,则返回当前对象的代理对象
return reactive(res);
}
return res;
}
}
function createSetter() {
return function set(target, key, value, receiver) {
const oldValue = target[key];
const hadKey = hasOwn(target, key);
const result = Reflect.set(target, key, value, receiver);
if (!hadKey) {
console.log('属性新增',key,value)
} else if (hasChanged(value, oldValue)) {
console.log('属性值被修改',key,value)
}
return result;
}
}
export const mutableHandlers = {
get, // 当获取属性时调用此方法
set // 当修改属性时调用此方法
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
这里我只对最常用到的get和set方法进行处理,还应该处理
has
、deleteProperty
、ownKeys
。这里为了快速掌握核心流程就先暂且跳过这些实现
使用reactive方法
let { computed, ref, reactive, effect } = Vue;
const proxy = reactive({name:'samy',age:11,lessons:['架构','高级']});
proxy.name = 'samy';
proxy.lessons[0] = '架构';
2
3
4
这里当我们获取属性和更改属性值时就可以触发对应的set和get方法
# effect实现
我们再来看effect的实现,默认effect会立即执行,当依赖的值发生变化时effect会重新执行
// 创建effect时可以传递参数,computed也是基于effect来实现的,只是增加了一些参数条件而已
export function effect(fn, options = {}) {
const effect = createReactiveEffect(fn, options);
if (!options.lazy) {
effect(); // 默认effect应该立即被执行
}
return effect;
}
let uid = 0;
const effectStack = []; // 存放effect的队列
let activeEffect; // 当前正在执行的effect
function createReactiveEffect(fn, options) {
const effect = function reactiveEffect() {
if (!effectStack.includes(effect)) {
try {
effectStack.push(effect); // 将当前effect放到栈中
activeEffect = effect; // 标记当前运行的effect
return fn(); // 执行用户定义的方法
} finally {
effectStack.pop(); // 执行完毕后出栈
activeEffect = effectStack[effectStack.length - 1];
}
}
}
effect.options = options; // effect所有属性
effect.id = uid++; // effect的标号
effect.deps = []; // effect函数对应的属性
return effect;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 依赖收集实现
在effect方法调用时会对属性进行取值,此时可以进行依赖收集。;以下图示分析的不错;
// 收集属性对应的effect
export function track(target, type, key) {}
// 触发属性对应effect执行
export function trigger(target, type, key) {}
2
3
4
operations.js
export const TrackOpTypes = {
GET: 'get'
}
export const TriggerOpTypes = {
SET: 'set',
ADD: 'add'
}
2
3
4
5
6
7
定义收集类型和触发类型
function createGetter() {
return function get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
// 取值时依赖收集
track(target, TrackOpTypes.GET, key);
if (isObject(res)) {
return reactive(res);
}
return res;
}
}
2
3
4
5
6
7
8
9
10
11
function createSetter() {
return function set(target, key, value, receiver) {
const oldValue = target[key];
const hadKey = hasOwn(target, key);
const result = Reflect.set(target, key, value, receiver);
if (!hadKey) {
// 设置值时触发更新 - ADD
trigger(target, TriggerOpTypes.ADD, key);
} else if (hasChanged(value, oldValue)) {
// 设置值时触发更新 - SET
trigger(target, TriggerOpTypes.SET, key, value, oldValue);
}
return result;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
track的实现
收集属性对应的effect
export function track(target, type, key) {
if (activeEffect == undefined) {
return;
}
let depsMap = targetMap.get(target);
if (!depsMap) { // 如果没有map,增加map
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key); // 取对应属性的依赖表
if (!dep) { // 如果没有则构建set
depsMap.set(key, (dep = new Set()));
}
if(!dep.has(activeEffect)){
dep.add(activeEffect);
activeEffect.deps.push(dep);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
trigger实现
触发属性对应effect执行
export function trigger(target, type, key) {
const depsMap = targetMap.get(target);
if (!depsMap) {
return;
}
const run = (effects) => {
if (effects) {effects.forEach(effect => effect());}
}
// 有key 就找到对应的key的依赖执行
if (key !== void 0) {
run(depsMap.get(key));
}
// 数组新增属性
if (type == TriggerOpTypes.ADD) {
run(depsMap.get(isArray(target) ? 'length' : ''));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# computed实现原理
- 计算属性也是一个effect,标记effect
lazy:true
和computed:true
,提供scheduler方法。在依赖数据更新时会调用schedular方法; - 计算属性会根据dirty值进行缓存;
- 计算属性需要标记
__v_isRef
说明计算属性取值时,会自动获取value属性;
import { isFunction } from "./util";
import { effect, track,trigger } from './effect'
import { TriggerOpTypes, TrackOpTypes } from "./operations";
export function computed(getterOrOptions) {
let getter;
let setter;
if (isFunction(getterOrOptions)) {
getter = getterOrOptions;
setter = () => {}
} else {
getter = getterOrOptions.get;
setter = getterOrOptions.set;
}
let dirty = true;
let computed;
let value;
let runner = effect(getter, {
lazy: true, // 默认不执行
computed: true, // 计算属性
scheduler: () => {
if (!dirty) {
dirty = true;
trigger(computed, TriggerOpTypes.SET, 'value')
}
}
})
computed = {
__v_isRef: true,
get value() {
if (dirty) {
value = runner(); // 取值时运行effect
dirty = false;
}
track(computed, TrackOpTypes.GET, 'value');
return value;
},
set value(newValue) {
setter(newValue)
}
}
return computed;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
trigger支持scheduler
当触发更新时需要分开执行计算属性和effect,计算属性的优先级高于effect,确保effect在执行时可以获得正确计算属性对应的结果;
export function trigger(target, type, key, value, oldValue) {
const depsMap = targetMap.get(target);
if (!depsMap) {
return;
}
const effects = new Set();
const computedRunners = new Set();
const add = (effectsToAdd) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect.options.computed) {
computedRunners.add(effect)
} else {
effects.add(effect)
}
})
}
}
if (key !== void 0) {
add(depsMap.get(key));
}
if (TriggerOpTypes.ADD) {
add(depsMap.get(isArray(target) ? 'length' : ''));
}
const run = (effect) => {
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
}
computedRunners.forEach(run)
effects.forEach(run)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# Ref实现原理
ref的原理就是将一个普通值,转化成对象,并且在获取和设置值时可以增加依赖收集和触发更新的功能;
import { isObject } from "lodash";
import { reactive } from "./reactive";
import { track, trigger } from "./effect";
import { hasChanged } from "./util";
import { TriggerOpTypes,TrackOpTypes } from "./operations";
export function ref(value) {
return createRef(value);
}
function convert(rawValue) {
return isObject(rawValue) ? reactive(rawValue) : rawValue
}
function createRef(rawValue) {
let value = convert(rawValue);
let r = {
__v_isRef: true,
get value() { // 取值依赖收集
track(r, TrackOpTypes.GET, 'value')
return value
},
set value(newVal) { // 设置时触发更新
if (hasChanged(newVal, rawValue)) {
rawValue = newVal;
value = newVal
trigger(r, TriggerOpTypes.SET, 'value')
}
}
}
return r
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 剖析diff算法【要点】
diff算法的核心就是子节点之间的比对,主要分为两种情况(子节点有key和无key的情况)
if (patchFlag & PatchFlags.KEYED_FRAGMENT) {
patchKeyedChildren(
c1 as VNode[],
c2 as VNodeArrayChildren,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
return
} else if (patchFlag & PatchFlags.UNKEYED_FRAGMENT) {
patchUnkeyedChildren(
c1 as VNode[],
c2 as VNodeArrayChildren,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
return
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 无Key的情况
当元素无key时,我们希望尽可能复用老节点
const patchUnkeyedChildren = (
c1: VNode[],
c2: VNodeArrayChildren,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
optimized: boolean
) => {
c1 = c1 || EMPTY_ARR
c2 = c2 || EMPTY_ARR
const oldLength = c1.length // 老节点长度
const newLength = c2.length // 新节点长度
// 计算能复用的节点
const commonLength = Math.min(oldLength, newLength)
let i
for (i = 0; i < commonLength; i++) {
const nextChild = (c2[i] = optimized
? cloneIfMounted(c2[i] as VNode)
: normalizeVNode(c2[i]))
patch(
c1[i],
nextChild,
container,
null,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
// 老元素多余新元素,将老元素卸载掉
if (oldLength > newLength) {
// remove old
unmountChildren(c1, parentComponent, parentSuspense, true, commonLength)
} else {
// 将多余的新元素挂载到老节点上
mountChildren(
c2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized,
commonLength
)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 有key的情况
diff算法的核心还是有key的情况的比对
let i = 0 // 开始索引
const l2 = c2.length;
let e1 = c1.length - 1 // 老节点最后的索引
let e2 = l2 - 1 // 新节点最后的索引
2
3
4
# 1.从头开始比对
while (i <= e1 && i <= e2) {
const n1 = c1[i]
const n2 = (c2[i] = optimized
? cloneIfMounted(c2[i] as VNode)
: normalizeVNode(c2[i]))
if (isSameVNodeType(n1, n2)) { // 如果是相同节点就做patch
patch(
n1,
n2,
container,
null,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else { // 否则跳出循环
break
}
i++
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 2.从尾部开始比对
while (i <= e1 && i <= e2) {
const n1 = c1[e1]
const n2 = (c2[e2] = optimized
? cloneIfMounted(c2[e2] as VNode)
: normalizeVNode(c2[e2]))
if (isSameVNodeType(n1, n2)) {
patch(
n1,
n2,
container,
null,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else { // 非相同节点跳出循环
break
}
e1-- // 移动指针
e2--
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 3.同序列挂载
if (i > e1) {
if (i <= e2) {
const nextPos = e2 + 1
const anchor = nextPos < l2 ? (c2[nextPos] as VNode).el : parentAnchor
while (i <= e2) { // 新节点多出来的插入到老节点中
patch(
null,
(c2[i] = optimized
? cloneIfMounted(c2[i] as VNode)
: normalizeVNode(c2[i])),
container,
anchor,
parentComponent,
parentSuspense,
isSVG
)
i++
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
新的节点个数多余老节点,(头部插入、尾部插入)
# 4.同序列卸载
else if (i > e2) {
while (i <= e1) {
unmount(c1[i], parentComponent, parentSuspense, true)
i++
}
}
2
3
4
5
6
老的节点个数多余新节点,(头部删除、尾部删除)
# 5.未知序列
# 5.1 根据key创建映射表
const s1 = i // 确定老节点开始位置
const s2 = i // 确定新节点开始位置
const keyToNewIndexMap: Map<string | number, number> = new Map()
for (i = s2; i <= e2; i++) {
const nextChild = (c2[i] = optimized
? cloneIfMounted(c2[i] as VNode)
: normalizeVNode(c2[i]))
if (nextChild.key != null) { // 创建key的映射表
keyToNewIndexMap.set(nextChild.key, i)
}
}
2
3
4
5
6
7
8
9
10
11
12
在这里我们需要将未patch的新节点根据key和索引制作成映射表,为后续复用逻辑做准备
# 5.2 循环老节点依次进行patch
let j
let patched = 0; // 标记已经patched个数
const toBePatched = e2 - s2 + 1 // 标记还需要patched的个数
let moved = false // 是否需要移动
let maxNewIndexSoFar = 0 // 临时标记
const newIndexToOldIndexMap = new Array(toBePatched) // 根据需要patched的个数创建数组
for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;
for (i = s1; i <= e1; i++) {
const prevChild = c1[i]
if (patched >= toBePatched) {
// all new children have been patched so this can only be a removal
unmount(prevChild, parentComponent, parentSuspense, true)
continue
}
// ...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
如果新节点没有需要patched,则循环老节点,将老节点依次进行卸载
let newIndex
if (prevChild.key != null) { // 通过老节点的key去新节点中找到对应索引
newIndex = keyToNewIndexMap.get(prevChild.key)
}
if (newIndex === undefined) { // 如果索引不存在则直接删除老节点
unmount(prevChild, parentComponent, parentSuspense, true)
}
2
3
4
5
6
7
for (j = s2; j <= e2; j++) {
if (
newIndexToOldIndexMap[j - s2] === 0 &&
isSameVNodeType(prevChild, c2[j] as VNode)
) {
newIndex = j
break
}
}
2
3
4
5
6
7
8
9
如果老节点无key,则会找到对应新节点看类型是否相同,如果类型相同则进行patch
newIndexToOldIndexMap[newIndex - s2] = i + 1
if (newIndex >= maxNewIndexSoFar) {
maxNewIndexSoFar = newIndex
} else {
moved = true
}
patch(
prevChild,
c2[newIndex] as VNode,
container,
null,
parentComponent,
parentSuspense,
isSVG,
optimized
)
patched++
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
标记已经patched过的元素,用于标记最长稳定序列,并且标记元素是否需要移动,最终值为0的则意味着元素没有被patch过
# 5.3 移动和挂载
const increasingNewIndexSequence = moved
? getSequence(newIndexToOldIndexMap) // 获取最长稳定序列
: EMPTY_ARR
j = increasingNewIndexSequence.length - 1
for (i = toBePatched - 1; i >= 0; i--) {
const nextIndex = s2 + i
const nextChild = c2[nextIndex] as VNode
const anchor =
nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor
if (newIndexToOldIndexMap[i] === 0) { // 如果为0 说明是新增元素
patch(
null,
nextChild,
container,
anchor,
parentComponent,
parentSuspense,
isSVG
)
} else if (moved) {
if (j < 0 || i !== increasingNewIndexSequence[j]) {
// 需要移动
move(nextChild, container, anchor, MoveType.REORDER)
} else {
j-- // 元素不需要移动
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
根据之前计算出来的数组,来确定最长增长稳定序列(不需要做移动的节点),循环新节点如果值为0则说明需要新增元素,否则查看节点是否需要移动
# 自定义渲染器实践
Vue3.0中支持 自定义渲染器 (Renderer):这个 API 可以用来创建自定义的渲染器, (在以往像weex和mpvue,需要通过fork源码的方式进行扩展)
# 自定义背景
这里我们来自定义一个canvas渲染器,可以渲染常见的饼图
借助Vue响应式的特性,实现图形渲染。
<template>
<div id="app" @click="handleClick">
<circle :data="state.data" :x="100" :y="300" :r="200"></circle>
</div>
</template>
<script>
import {reactive,ref} from 'vue'
export default {
setup(){
const state = reactive({
data:[
{name:'语文',count:200,color:'red'},
{name:'物理',count:100,color:'yellow'},
{name:'数学',count:300,color:'gray'},
{name:'化学',count:200,color:'pink'},
]
});
function handleClick(){
state.data.push( {name:'英语',count:30,color:'green'})
}
return {
state,
handleClick
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
重写mount方法,生成cavans并进行挂载操作,这里的nodeOps是需要提供的
api
,Vue在渲染时会调用用户提供的方法,从而达到自定义渲染器的目的!
import { createRenderer } from '@vue/runtime-dom';
let renderer = createRenderer(nodeOps);
let ctx;
let canvas;
function createApp(App) {
const app = renderer.createApp(App);
return {
mount(selector) {
canvas = document.createElement('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.querySelector(selector).appendChild(canvas);
ctx = canvas.getContext('2d');
app.mount(canvas);
}
}
}
createApp(App).mount('#app')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 自定义渲染逻辑
这里我们改写
patchProp
、insert
、createElement
方法。
patchProp
每次更新属性会调用此方法insert
元素插入到页面中会调用此方法createElement
创建元素会调用此方法
const nodeOps = {
insert: (child, parent, anchor) => {
child.parent = parent;
if (!parent.childs) { // 格式化父子关系
parent.childs = [child]
} else {
parent.childs.push(child);
}
if (parent.nodeType == 1) {
draw(child); // 开始绘图
if (child.onClick) {
ctx.canvas.addEventListener('click', () => {
child.onClick();
setTimeout(() => {
draw(child)
}, 0);
}, false)
}
}
},
remove: child => {},
createElement: (tag, isSVG, is) => {
return {tag}
},
createText: text => {},
createComment: text => {},
setText: (node, text) => {},
setElementText: (el, text) => {},
parentNode: node => {},
nextSibling: node => {},
querySelector: selector => {},
setScopeId(el, id) {},
cloneNode(el) {},
insertStaticContent(content, parent, anchor, isSVG) {},
patchProp(el, key, prevValue, nextValue) {
el[key] = nextValue;
},
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 提供draw方法
针对tag进行渲染操作,并遍历子元素。在子元素渲染的过程中不需要清除画布。
const draw = (el,noClear) => {
if (!noClear) {
ctx.clearRect(0, 0, canvas.width, canvas.height)
}
if (el.tag == 'circle') {
let { data, r, x, y } = el;
let total = data.reduce((memo, current) => memo + current.count, 0);
let start = 0,
end = 0;
data.forEach(item => {
end += item.count / total * 360;
drawCircle(start, end, item.color, x, y, r);
drawCircleText(item.name, (start + end) / 2, x, y, r);
start = end;
});
}
el.childs && el.childs.forEach(child => draw(child,true));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 饼图绘制
这里就是普通的canvas操作;
const d2a = (n) => {
return n * Math.PI / 180;
}
const drawCircle = (start, end, color, cx, cy, r) => {
let x = cx + Math.cos(d2a(start)) * r;
let y = cy + Math.sin(d2a(start)) * r;
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.lineTo(x, y);
ctx.arc(cx, cy, r, d2a(start), d2a(end), false);
ctx.fillStyle = color;
ctx.fill();
ctx.stroke();
ctx.closePath();
}
const drawCircleText = (val, posistion, cx, cy, r) => {
ctx.beginPath();
let x = cx + Math.cos(d2a(posistion)) * r/1.25 - 20;
let y = cy + Math.sin(d2a(posistion)) * r/1.25;
ctx.fillStyle = '#000';
ctx.font = '20px 微软雅黑';
ctx.fillText(val,x,y);
ctx.closePath();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24