源码精读:Vue3系列(连载中)

442 阅读10分钟

Vue系列

Vue3

源码结构

compiler-sfc将SFC转为JS代码,依赖了compiler-core和compiler-dom

compiler-core将Template转为Render函数

reactivity核心流程

所谓响应式,就是当我们修改数据后,可以自动做某些事情;对应到组件的渲染,就是修改数据后,能自动触发组件的重新渲染。

Vue 3 实现响应式,本质上是通过 Proxy API 劫持了数据对象的读写,当我们访问数据时,会触发 getter 执行依赖收集;修改数据时,会触发 setter 派发通知。

packages\reactivity\src\reactive.ts文件

先通过createReactiveObject()创建Proxy对象

Proxy主要传入两个参数:

  1. target:需要代理的目标
  2. baseHandlers:响应式对象的处理器

这里使用了weekMap做缓存优化,在createReactiveObject()中,先判断缓存中是否已经有了该代理对象,有的话直接返回回去,没有就new Proxy(target,baseHandlers),再将new的Proxy存入weekMap缓存中。

function createReactiveObject(target, proxyMap, baseHandlers) {
  // 核心就是 proxy
  // 目的是可以侦听到用户 get 或者 set 的动作

  // 如果命中的话就直接返回就好了
  // 使用缓存做的优化点
  const existingProxy = proxyMap.get(target);
  if (existingProxy) {
    return existingProxy;
  }

  const proxy = new Proxy(target, baseHandlers);

  // 把创建好的 proxy 给存起来,
  proxyMap.set(target, proxy);
  return proxy;
}

baseHandlers():

packages\reactivity\src\baseHandlers.ts

主要用于代理响应式数据对象实现数据在不同场景下的自动更新。具体来说,baseHandlers通过重写数据对象的get、set、delete等操作,实现了数据的响应式更新和数据依赖的收集和计算

处理器Handlers中的get,set主要通过createGetter(),createSetter()函数创建,

里面使用了Reflect.xxx:在外界更新、读取值等操作时,触发set、get...等,将新值传入如,再返回新值,同时触发trigger()或track();

track()和trigger():
packages\reactivity\src\effect.ts
  • track()函数主要用于收集依赖,记录数据属性被哪些计算属性或者watcher所依赖。每当一个计算属性或者watcher访问一个数据属性时,track()就会被调用,用来收集这个依赖关系。
    • 例如,在模板中使用了一个计算属性,这个计算属性依赖于data中的一个属性,那么track()函数就会收集这个依赖关系。
  • trigger()函数主要用于触发依赖,当一个数据属性发生变化时,trigger()会被调用,用来通知计算属性或者watcher去重新计算和渲染。
    • 例如,当上面的依赖关系中,data中的属性发生变化时,trigger()函数就会被调用,用来触发更新计算属性或者watcher的操作

先基于 target 找到对应的 dep, 如果是第一次的话,那么就需要初始化,再将dep放入全局缓存targetMap

triggger()先收集所有的 dep 放到 deps 里面

runtime-core模板编译核心流程

1.创建App根组件

1.1CreateApp(App)

初始化:main.ts调用mount()

2.1 基于root Component生成vnode
2.2 进行render——>调用patch():基于vnode的类型对不同类型组件进行处理
function patch(n1, n2, container = null, anchor = null, parentComponent = null) {
        const { type, shapeFlag } = n2;
        switch (type) {
            case Text:
                processText(n1, n2, container);
                break;
            case Fragment:
                processFragment(n1, n2, container);
                break;
            default:
                if (shapeFlag & 1) {
                    console.log("处理 element");
                    processElement(n1, n2, container, anchor, parentComponent);
                }
                else if (shapeFlag & 4) {
                    console.log("处理 component");
                    processComponent(n1, n2, container, parentComponent);
                }
        }
    }

n1为之前的值,n2为当前的值,用于组件更新的diff算法。

如果n1为null,则表示这是在 组件初始化,则执行mountComponent() ,否则就为 组件更新,执行updateComponent()

 const processComponent = (
    n1: VNode | null,
    n2: VNode,
    container: RendererElement,
    anchor: RendererNode | null,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    slotScopeIds: string[] | null,
    optimized: boolean
  ) => {
    n2.slotScopeIds = slotScopeIds
    if (n1 == null) {
      if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
        ;(parentComponent!.ctx as KeepAliveContext).activate(
          n2,
          container,
          anchor,
          isSVG,
          optimized
        )
      } else {
        //初始化组件
        mountComponent(
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          isSVG,
          optimized
        )
      }
    } else {
      //更新组件
      updateComponent(n1, n2, optimized)
    }
  }
初始化组件mountComponent()
  1. 创建Component组件实例对象
  2. setup component
    1. 初始化props :initProps(instance, props):就是将给实例的props属性赋值  instance.props = Props;
    2. 初始化slots: initSlots(instance, children)
    3. setupStatefulComponent
      1. 创建一个proxy代理对象,并绑定到instance.proxy
      2. 设置组件实例对象: setCurrentInstance(instance)。这样使用者就可以通过getCurrentInstance()API获取当前组件实例。
      3. 设置上下文对象:createSetupContext(instance): context会传入setup函数
      4. 调用setup()函数,并拿到返回值result:setup(shallowReadonly(instance.props), setupContext)
        1. 如果返回值是function(setup里面return一个函数):会将这个函数当初render()函数处理。也可以在setup外另外定义一个render()函数。
        2. 如果返回值是object : 将用户写的template编译成render函数,再将其挂载到instance.render
        3. 注意:以上步骤并未调用render函数,只是设置了render函数
  1. setupRenderEffect
    1. renderComponentRoot()调用用户定义的render()函数,获取vnode
    2. 调用patch(),初始化子组件。(这是一个递归), 这时,子组件的shapeFlag变成了Element类型.
      1. processElement(n1, n2, container, anchor, parentComponent)
        1. 判断n1是否null,mountElement()patchElement()
        2. 初始化element :
          1. hostCreateElement()创建真实元素。源码中这些函数是在packages\runtime-dom\src\nodeOps.ts中实现的。这样就vue可以自定义渲染器,比如改写成渲染到canvas上。
          2. 处理children节点:mountChildren() :遍历所有子节点,递归调用patch()。
          3. 设置元素props : hostPatchProp(),也是runtime-dom的api,底层是setAttribute
          4. 将当前节点插入到父节点中: hostInsert(el, container, anchor)
        1. 更新element
  1. 所有根元素创建完成后, hostInsert("#root") 将其插入到#root元素上

Update:

当响应式数值发生变化时触发update流程,例如count++,触发当前组件的effect()

  1. mountComponent()时的setupRenderEffect()阶段,执行effect()
  2. 执行componentUpdateFn() else分支,主要就是拿到新的 vnode ,然后和之前的 vnode 进行对比
    1. 先更新组件的数据: updateComponentPreRender()
    2. 调用render() 函数拿到最新的组件树
    3. 替换子树, 将之前的节点数,用prevTree变量存储,将实例上的subTree换为最新的。
    4. 调用patch(prevTree, nextTree, prevTree.el, null, instance);
      1. processElement()——>updateElements(n1,n2,container):
        1. 获取新旧Props
        2. 将旧vnode的el赋值给新el,以此保持新节点的完整性
        3. 对比Props: patchProps()
          1. 遍历新Props,如果与旧Props不一样则 执行hostPatchProp()removeAttribute
        1. 对比Children: patchChildren(n1, n2, el, anchor, parentComponent);
          1. Text类型,且不一样,直接使用hostSetElementText()innerText
          2. 其他类型,且不一样,patchKeyedChildren() ,diff算法核心:
// 在 vue3.2 版本里面是使用的 new ReactiveEffect
// 至于为什么不直接用 effect ,是因为需要一个 scope  参数来收集所有的 effect
// 而 effect 这个函数是对外的 api ,是不可以轻易改变参数的,所以会使用  new ReactiveEffect
// 因为 ReactiveEffect 是内部对象,加一个参数是无所谓的
// 后面如果要实现 scope 的逻辑的时候 需要改过来
// 现在就先算了
//简易版
instance.update = effect(componentUpdateFn, {
  scheduler: () => {
    // 把 effect 推到微任务的时候在执行
    // queueJob(effect);
    queueJob(instance.update);
  },
});


//源码版
const effect = (instance.effect = new ReactiveEffect(
  componentUpdateFn,
  () => queueJob(update),
  instance.scope // track it in component's effect scope
))

effect数据结构

源码实战

环境搭建

Babel,Jest,TS

响应式

测试用例:

import { effect, stop } from "../effect";
import { reactive } from "../reactive";

//测试effect
describe("effect", () => {
	it.skip("happy path", () => {
		const user = reactive({
			age: 10,
		});
		let nextAge;
		effect(() => {
			nextAge = user.age + 1;
		});
		expect(nextAge).toBe(11);
		//数值更新,希望触发effect()
		user.age++;
		expect(nextAge).toBe(12);
	});
//测试获取effect的返回值
	it("should return runner", () => {
		let foo = 10;
		const runner = effect(() => {
			foo++;
			return "foo";
		});
		expect(foo).toBe(11);
		const r = runner();
		expect(foo).toBe(12);
		expect(r).toBe("foo");
	});

  
	it("scheduler", () => {
		//1.通过 effect 的第二个参数给定的一个 scheduler 的fn
		//2.effect第一次执行时,还会执行fn
		//3.当响应式对象set,update时,不会执行fn,而是执行scheduler
		//4.如果说 当执行runner时,会再次执行fn
		let dummy;
		let run: any;
		const scheduler = jest.fn(() => {
			run = runner;
		});
		const obj = reactive({ foo: 1 });
		const runner = effect(
			() => {
				dummy = obj.foo;
			},
			{ scheduler }
		);
		//断言一开始不会被调用
		expect(scheduler).not.toHaveBeenCalled();
		expect(dummy).toBe(1);
		//断言当响应式值发生改变时,不会调用effect的第一个参数,
		// 而是会调用scheduler
		// should be called on first trigger
		obj.foo++;
		expect(scheduler).toHaveBeenCalledTimes(1);
		// should not run yet
		expect(dummy).toBe(1);
		// manually run
		run();
		// should have run
		expect(dummy).toBe(2);
	});

	//取消响应式功能
	it("stop", () => {
		let dummy;
		const obj = reactive({ prop: 1 });
		const runner = effect(() => {
			dummy = obj.prop;
		});
		obj.prop = 2;
		expect(dummy).toBe(2);
		stop(runner);
		obj.prop = 3;
		expect(dummy).toBe(2);

		// stopped effect should still be manually callable
		runner();
		expect(dummy).toBe(3);
	});
	//调用stop后的回调函数
	it("events: onStop", () => {
		const obj = reactive({ prop: 1 });
		const onStop = jest.fn();
		let dummy;
		const runner = effect(
			() => {
				dummy = obj.foo;
			},
			{
				onStop,
			}
		);

		stop(runner);
		expect(onStop).toHaveBeenCalled();
	});
});
import { reactive } from "../reactive";

describe("reactive", () => {
	it("happy path", () => {
		const original = { foo: 1 };
		const observed = reactive(original);
		//来验证源对象与响应式后的对象不相等
		expect(observed).not.toBe(original);
		//用来验证响应式对象与和源对象的一致性
		expect(observed.foo).toBe(1);
	});
});
Reactive
  1. 创建,并返回Proxy
  2. get 触发收集依赖track()
  3. set 触发依赖trigger()
import { readonly } from "../reactive";

//readonly不可以被改写(set)
describe("readonly", () => {
	it("happy path", () => {
		const original = { foo: 1, bar: { baz: 2 } };
		const observed = readonly(original);
		expect(observed).not.toBe(original);
		expect(observed.foo).toBe(1);
	});
});
import { mutableHandlers, readonlyHandlers } from "./baseHandlers";
import { track, trigger } from "./effect";

export function reactive(raw) {
  return new Proxy(
    raw,
    {
      get(target, key) {
        //target:代理的对象
        //key:要get的代理对象的属性
        const res = Reflect.get(target, key);
        //TODO 依赖收集
        track(target, key);
        return res;
      },
      set(target, key, value) {
        const res = Reflect.set(target, key, value);
        //TODO 触发依赖
        trigger(target, key, value);
        return res;
      },

    }
  );
}

//readonly不能set,没有track()
export function readonly(raw) {
  return new Proxy(
    raw,
    readonlyHandlers
    {
    get(target, key) {
      const res = Reflect.get(target, key);
      return res;
    },
    set(target, key, value) {
      throw new Error("readonly不可被更改");
    }
  }
  );
}

我们可以发现,get和set函数的逻辑重复了,我们可以将其抽取成 createGetter()createSetter()

import { track, trigger } from "./effect";
//重构get,set更具通用性.
function createGetter(isReadonly = false) {
  return function get(target, key) {
    const res = Reflect.get(target, key);
    //非只读,则可以触发track()
    if (!isReadonly) {
      track(target, key);
    }
    return res;
  };
}

function createSetter() {
  return function set(target, key, value) {
    const res = Reflect.set(target, key, value);
    trigger(target, key, value);
    return res;
  };
}

export function reactive(raw) {
  return new Proxy(
    raw,
    {
      get: createGetter(),
  		set: createSetter()
    }
  );
}

//readonly不能set,没有track()
export function readonly(raw) {
  return new Proxy(
    raw,
    {
      get: createGetter(true),
      set: createSetter(),
    }
  );
}

我们又发现,整个Proxy的第二个参数都可以抽离到另一个文件baseHandlers.ts,用于存放所有Handler,甚至创建整个new Proxy都可以抽离成一个函数createActiveObject()用于创建代理。

import { mutableHandlers, readonlyHandlers } from "./baseHandlers";
import { track, trigger } from "./effect";

export function reactive(raw) {
  return createActiveObject(raw, mutableHandlers);
}

//readonly不能set,没有track()
export function readonly(raw) {
  return createActiveObject(raw, readonlyHandlers);
}

function createActiveObject(raw: any, baseHandlers) {
  return new Proxy(raw, baseHandlers);
}
import { track, trigger } from "./effect";

//重构get,set更具通用性.
function createGetter(isReadonly = false) {
  return function get(target, key) {
    const res = Reflect.get(target, key);
    //非只读,则可以触发track()
    if (!isReadonly) {
      track(target, key);
    }
    return res;
  };
}
function createSetter() {
  return function set(target, key, value) {
    const res = Reflect.set(target, key, value);
    trigger(target, key, value);
    return res;
  };
}
//reactive的handler
export const mutableHandlers = {
  get: createGetter(),
  set: createSetter(),
};

//readonly的handler
export const readonlyHandlers = {
  get: createGetter(true),
  set: createSetter(),
};

每次都要重新创建getter,setter。我们可以对getter,setter做缓存,一开始就创建好这两个。

import { track, trigger } from "./effect";

const get = createGetter();
const set = createSetter();
const readonlyGet = createGetter(true);
//重构get,set更具通用性.
function createGetter(isReadonly = false) {
	return function get(target, key) {
		const res = Reflect.get(target, key);
		//非只读,则可以触发track()
		if (!isReadonly) {
			track(target, key);
		}
		return res;
	};
}

function createSetter() {
	return function set(target, key, value) {
		const res = Reflect.set(target, key, value);
		trigger(target, key, value);
		return res;
	};
}
export const mutableHandlers = {
	get,
	set,
};

export const readonlyHandlers = {
	readonlyGet,
	set(target, key, value) {
		console.warn(`${key} set失败 ,因为target为readonly的`);
		return true;
	},
};
重构reactive.ts
effect

_effect是每个ReactiveEffect的实例,他代表每个响应式dep。

对reactive重构

思维导图来源:催学社催学社 (cuixueshe.com)

原文是在语雀编写的,格式有点不对请见谅