vue3自定义实现

# 总体把握

主体

  • Vue3开发环境搭建,手写Vue3响应式模块
  • 手写Vue3初始化流程实现、虚拟DOM实现、组件实现
  • 手写Vue3diff算法实现
  • 手写 watchApi 异步渲染 、生命周期实现 、vue中AST转化 (额外:vue3 组件库搭建)
  • 手写模板编译 transform 核心逻辑
  • 手写模板编译template -> render函数逻辑
  • Vue3 源码剖析及调试技巧 + 其他api实现原理 + 单元测试

其他

  • ts课程:类、接口、泛型、兼容性、条件、类型保护、声明文件.....等
  • vue3项目实战
  • vue3其他生态源码剖析 vue-routervuexvite

# 架构分析/环境搭建

# 区别介绍

  • 源码采用 monorepo 方式进行管理,将模块拆分到package目录中;
  • Vue3 采用ts开发,增强类型检测。 Vue2 则采用flow
  • Vue3的性能优化,支持tree-shaking, 不使用就不会被打包;
  • Vue2 后期引入RFC , 使每个版本改动可控 rfcs (opens new window)

内部代码优化

  • Vue3 劫持数据采用proxy Vue2 劫持数据采用definePropertydefineProperty有性能问题和缺陷;
  • Vue3中对模板编译进行了优化,编译时 生成了Block tree,可以对子节点的动态节点进行收集,可以减少比较,并且采用了 patchFlag 标记动态节点;
  • Vue3 采用compositionApi 进行组织功能,解决反复横跳,优化复用逻辑 (mixin带来的数据来源不清晰、命名冲突等), 相比optionsApi 类型推断更加方便;
  • 增加了 Fragment,TeleportSuspense组件;

# 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:完整版本,包括运行时和编译器

image-20211230115330626

# 安装依赖

依赖
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
1
2

# workspace配置

npm init -y && npx tsc --init
1

其实这个是对应lerna.json的配置; lerna init生成的; 【lerna相关不用设置】

{
  "packages": [
    "packages/*"
  ],
  "version": "0.0.0"
}
1
2
3
4
5
6

把这个配置lerna合并到pkg中;

package.json配置

{
  "private":true,
  "workspaces":[
    "packages/*"
  ],
}
1
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
1
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"
    ]
  },
}
1
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"
      ]
    }
}
1
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)
1
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) {}
1
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 installshared模块注入到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"
}
1
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; 
}
1
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;
    }
};
1
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;
}
1
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];
        }
    }
}
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
})
1
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);
        }
    }
}
1
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);
    }
}
1
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;
    }
}
1
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();
    })
}
1
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);
        }
    }
}
1
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
    }
}
1
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);
  }
}
1
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();
 })
1
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);
1
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-domruntime-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;
    }
}
1
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;// 设置样式名
}
1
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] = '';
                }
            }
        }
    }
}
1
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;
}
1
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);
    }
}
1
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);
      }
    }
  },
}
1
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
}
1
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;
        }
    }
}
1
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)
    }
}
1
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;
    }
}
1
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;
    }
}
1
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
}
1
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
}
1
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
}
1
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
    ))
}
1
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
}
1
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)
}
1
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
}
1
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;
    }
}
1
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)
}
1
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);
}
1
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);
}
1
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('更新逻辑')
        }
    })
}
1
2
3
4
5
6
7
8
9
10
11
12
13

render函数中返回的是虚拟节点,例如

const App = {
    render : (r) =>h('div', {}, 'hello sy')
}
1
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)
    }
}
1
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)
}
1
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)
    }
}
1
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));
}
1
2
3
4
5
6
7
const processText = (n1,n2,container) =>{
    if(n1 == null){ // 创建文本插入到容器中
        hostInsert(n2.el = hostCreateText(n2.children),container)
    }
}
1
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); // 处理组件类型
        }
    }
  };
1
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',
}
1
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);
1
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)
  }
}
1
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
})
1
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();
}
1
2
3
4
5
6
7
8
9
10
11

# 编译原理

compiler-dom模块目录下;

# 模板调试

Vue中对template属性会编译成render方法。vue-next源码可以直接运行命令实现在线调试。打开网址:本地地址 (opens new window)

npm run dev-compiler
1

# 模板编译步骤

export function baseCompile(template) {
    // 1.生成ast语法树
    const ast = baseParse(template);
    // 2.转化ast语法树
    transform(ast)
    // 3.根据ast生成代码
    return generate(ast);
}
1
2
3
4
5
6
7
8

# 生成AST语法树

创建解析上下文,开始进行解析

function baseParse(content) {
    // 创建解析上下文,在整个解析过程中会修改对应信息
    const context = createParserContext(content);
    // 解析孩子代码
    return parseChildren(context);
}
1
2
3
4
5
6
function createParserContext(content) {
    return {
        column: 1, // 列数
        line: 1, // 行数
        offset: 0, // 偏移字符数
        originalSource: content, // 原文本不会变
        source: content // 解析的文本 -> 不停的减少
    }
}
1
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
}
1
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
}
1
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)
    }
}
1
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 };
}
1
2
3
4
function parseTextData(context, endIndex) { // 截取文本部分,并删除文本
    const rawText = context.source.slice(0, endIndex);
    advanceBy(context, endIndex);
    return rawText;
}
1
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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

解析结果:

const {baseCompile} = VueCompilerDOM;
console.log(baseCompile(`syjg`))
1
2
content: "syjg"
loc:
    end: {column: 5, line: 1, offset: 4}
    source: "syjg"
    start: {column: 1, line: 1, offset: 0}
type: 2
1
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)
    }
}
1
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  }}`))
1
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
1
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
}
1
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)
    }
}
1
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>`))
1
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
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
    }
}
1
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
}
1
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
    }
}
1
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) }
}
1
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)
}
1
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)
    )
}
1
2
3
4
5
6
7
8
9
10

将解析出的节点,再次进行包裹 ast.ts

export function createRoot(children,loc){
    return {
        type:NodeTypes.ROOT,
        children,
        loc
    }
}
1
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"
}
1
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 })
}
1
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
    ]// ...指令转化
}
1
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
function transform(root, options) {
    const context = createTransformContext(root, options);
    traverseNode(root, context)
}
1
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;
    }
}
1
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); // 遍历节点
    }
}
1
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);
        }
    }
}
1
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]()
    }
}
1
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
                        )
                    }
                }
            }
        }
    }
}
1
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;
}
1
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);
    }
}
1
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]
}
1
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"
}
1
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); // 生成代码时所需要的上下文
}
1
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
}
1
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`);
1

生成return语句

genNode(ast.codegenNode, context)
1
# 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;
    }
}
1
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)
1
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(`)`)
    }
}
1
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(',')
        }
    }
}
1
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(']')
}
1
2
3
4
5
6
7
8

# 2.对元素节点转化

case NodeTypes.ElEMENT:
    genNode(node.codegenNode, context)
1
2

# 3.对文本节点转化

case NodeTypes.TEXT:
    genText(node, context);
1
2
function genText(node,context) {
    context.push(JSON.stringify(node.content), node)
}
1
2
3

# 4.对表达式的转化

case NodeTypes.INTERPOLATION:
	genInterpolation(node, context)
1
2
function genInterpolation(node, context) {
    const { push, helper } = context
    push(`${helper(TO_DISPALY_STRING)}(`)
    genNode(node.content, context)
    push(`)`)
}
1
2
3
4
5
6

# 5.简单表达式

case NodeTypes.SIMPLE_EXPRESSION:
    genExpression(node, context)
1
2
function genExpression(node, context) {
    const { content, isStatic } = node
    context.push(content)
}
1
2
3
4

# 6.复合表达式

case NodeTypes.COMPOUND_EXPRESSION:
    genCompoundExpression(node, context)
1
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)
        }
    }
}
1
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
1
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(`)`)
}
1
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);
1
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
        }
    }
}
1
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);
1
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]();
        }
    }
}
1
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'
1
2
3

watchEffect实现

export function watchEffect(source,options){
    dowatch(source, null, options)
}
1
2
3
function dowatch(source, cb, {flush,immediate}) {
    const job = () => {
        if(cb){
            // ....
        }else{ // watchEffect 不需要新旧对比
            runner()
        }
    }
    if(cb){
       // ...
    }else{ // watchEffect 默认会执行一次
        runner();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

导出:

// export { computed } from './apiComputed'
export { watch, watchEffect } from './apiWatch'
1
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');
1
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
    })
}
1
2
3
4
5
6
7

queueJob的原理也是将渲染函数维护到队列中

let queue: any = []
export function queueJob(job) {
    if (!queue.includes(job)) {
        queue.push(job);
        queueFlush();
    }
}
1
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]();
    }
  }
}
1
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); // 稍后刷新任务队列
    }
}
1
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';
1
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>
1
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();
}
1
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);
1
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
}
1
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);
    }
}
1
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);
}
1
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
        })
    }
}
1
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);
1
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); // 将子树挂载在元素上
}
1
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);
})
1
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;
        }
    })
}
1
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;
        }
    })
}
1
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'
                    }
                }
            ]
        })
    }
}
1
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
1

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'
        })
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

配置.babelrc文件

{
    "presets": ["@babel/preset-env"]
}
1
2
3

执行脚本配置

"scripts": {
    "build:dev": "webpack --mode development",
    "serve": "webpack-dev-server"
}
1
2
3
4

使用Vue3响应式模块

import { reactive, effect } from '@vue/reactivity'
let state = reactive({
    name: 'samy',
    age: 11,
});
effect(() => {
    console.log(state.name)
});
1
2
3
4
5
6
7
8

# 目录结构搭建

  • computed.js
  • effect.js
  • index.js
  • reactive.js
  • ref.js

这里我们要实现的方法分别用 reactiveeffectrefcomputed方法。在index文件中统一整合这些方法进行导出

export {computed} from './computed';
export {effect} from './effect';
export {reactive} from './reactive';
export {ref} from './ref';
1
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
}
1
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 // 当修改属性时调用此方法
}
1
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方法进行处理,还应该处理hasdeletePropertyownKeys。这里为了快速掌握核心流程就先暂且跳过这些实现

使用reactive方法

let { computed, ref, reactive, effect } = Vue;
const proxy = reactive({name:'samy',age:11,lessons:['架构','高级']});
proxy.name = 'samy';
proxy.lessons[0] = '架构';
1
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;
}
1
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方法调用时会对属性进行取值,此时可以进行依赖收集。;以下图示分析的不错;

img

// 收集属性对应的effect
export function track(target, type, key) {}
// 触发属性对应effect执行
export function trigger(target, type, key) {}
1
2
3
4

operations.js

export const TrackOpTypes = {
    GET: 'get'
}
export const TriggerOpTypes = {
    SET: 'set',
    ADD: 'add'
}
1
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;
    }
}
1
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;
    }
}
1
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);
    }
}
1
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' : ''));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# computed实现原理

  • 计算属性也是一个effect,标记effect lazy:truecomputed: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;
}
1
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)
}
1
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
}
1
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
}
1
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
      )
    }
  }
1
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

img

img

# 有key的情况

diff算法的核心还是有key的情况的比对

let i = 0 // 开始索引
const l2 = c2.length;
let e1 = c1.length - 1 // 老节点最后的索引
let e2 = l2 - 1  // 新节点最后的索引
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++
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

img

# 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--
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

img

# 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++
    }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

新的节点个数多余老节点,(头部插入、尾部插入)

img img

# 4.同序列卸载

else if (i > e2) {
    while (i <= e1) {
        unmount(c1[i], parentComponent, parentSuspense, true)
        i++
    }
}
1
2
3
4
5
6

老的节点个数多余新节点,(头部删除、尾部删除)

img img

# 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)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

在这里我们需要将未patch的新节点根据key和索引制作成映射表,为后续复用逻辑做准备

img

# 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
    }
    // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

如果新节点没有需要patched,则循环老节点,将老节点依次进行卸载

img

let newIndex
if (prevChild.key != null) { // 通过老节点的key去新节点中找到对应索引
    newIndex = keyToNewIndexMap.get(prevChild.key)
}
if (newIndex === undefined) { // 如果索引不存在则直接删除老节点
    unmount(prevChild, parentComponent, parentSuspense, true)
} 
1
2
3
4
5
6
7

img

for (j = s2; j <= e2; j++) {
    if (
        newIndexToOldIndexMap[j - s2] === 0 &&
        isSameVNodeType(prevChild, c2[j] as VNode)
    ) {
        newIndex = j
        break
    }
}
1
2
3
4
5
6
7
8
9

如果老节点无key,则会找到对应新节点看类型是否相同,如果类型相同则进行patch

img

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++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

标记已经patched过的元素,用于标记最长稳定序列,并且标记元素是否需要移动,最终值为0的则意味着元素没有被patch过

img

# 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-- // 元素不需要移动
    }
}
}
1
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则说明需要新增元素,否则查看节点是否需要移动

img

# 自定义渲染器实践

Vue3.0中支持 自定义渲染器 (Renderer):这个 API 可以用来创建自定义的渲染器, (在以往像weex和mpvue,需要通过fork源码的方式进行扩展)

# 自定义背景

这里我们来自定义一个canvas渲染器,可以渲染常见的饼图

img

借助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>
1
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')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 自定义渲染逻辑

这里我们改写 patchPropinsertcreateElement方法。

  • 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;
    },
};
1
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));
}
1
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();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 参考链接

上次更新: 2022/04/15, 05:41:28
×