vue3源码解析

408 阅读7分钟

一、如何解读?

初级

// main.js
import { createApp } from 'vue'
import App from './app.vue'
createApp(App).mount('#app')


// app.vue

<script>
import { ref } from 'vue'
export default {
  setup(props,context) {
    // 透传 Attributes(非响应式的对象,等价于 $attrs)
    console.log(context.attrs)
    // 插槽(非响应式的对象,等价于 $slots)
    console.log(context.slots)
    // 触发事件(函数,等价于 $emit)
    console.log(context.emit)
    // 暴露公共属性(函数)
    console.log(context.expose)

    const count = ref(0)
    return {
      count
    }
  },
  mounted() {
    console.log(this.count) // 0
  }
}
</script>
<template>
  <button @click="count++">{{ count }}</button>
</template>

或者

<script setup>
import { ref, onMounted } from 'vue'

const props = defineProps({foo: String})
const emit = defineEmits(['change', 'delete'])

const count = ref(0)
function increment() {
  count.value++
}
onMounted(() => {
  console.log(`The initial count is ${count.value}.`)
})
</script>
<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

中级

  • 先了解vue2源码实现,查看文章

  • 对着 mini-vue 一行一行去看 setup、ref、reactive如何实现

高级
自己对着源码看吧 ~

二、mini-vue 解读流程

ps:只实现了组合式 API的形式,下面只讨论组合式API写法

目录如下:
从package.json中命令我们知道使用了rollup编译,入口在"./packages/vue/src/index.ts";
import了"@mini-vue/runtime-dom",而runtime-dom又export了"@mini-vue/reactivity"

  • runtime-dom:export了createApp函数和runtime-core目录,这些都是我们在import Vue能拿到的
  • runtime-core:渲染部分
  • reactivity:响应式部分
  • compiler-core:编译部分
  • shared:一些公共函数和常量

image.png

流程图如下

image.png

1.、入口 createApp(App).mount('#app')

createApp在/runtime-dom/src/index.ts里

export const createApp = (...args) => {
  return ensureRenderer().createApp(...args);
};

function ensureRenderer() {
  return createRenderer()
}

//runtime-core/src/renderer.ts
export function createRenderer(options) {
 return {
    render,
    createApp: createAppAPI(render),
  }
}

// runtime-core/src/createApp.ts
export function createAppAPI(render) {
  return function createApp(rootComponent) {
    return {
      _component: rootComponent,
      mount(rootContainer) {
        const vnode = createVNode(rootComponent);
        render(vnode, rootContainer);
      },
    };
  };
}

由上面的逻辑知道,createApp(App).mount('#app')后执行createVNode创建虚拟dom,然后进入render方法

  const vnode = {
    el: null,
    component: null,
    key: props?.key,
    type,
    props: props || {},
    children,
    shapeFlag: getShapeFlag(type)
 }

render方法是调用createAppAPI传入,在runtime-core/src/renderer.ts,

const render = (vnode, container) => {
   patch(null, vnode, container);
}

2、patch 视图渲染

patch 中不同的节点类型走不同逻辑

 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:
      // 这里就基于 shapeFlag 来处理
      if (shapeFlag & ShapeFlags.ELEMENT) {
        console.log("处理 element");
        processElement(n1, n2, container, anchor, parentComponent);
      } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
        console.log("处理 component");
        processComponent(n1, n2, container, parentComponent);
      }
  }
}

入口处createApp(App),App一般都是组件类型。

//  type一般格式
{
    name: "App",
    setup() {},
    render() {}
}

processComponent 组件渲染

  function processComponent(n1, n2, container, parentComponent) {
    // 如果 n1 没有值的话,那么就是 mount
    if (!n1) {
      // 初始化 component
      mountComponent(n2, container, parentComponent);
    } else {
      updateComponent(n1, n2, container);
    }
}

组件初次渲染

  • 创建一个instance对象
  • setupComponent
  • setupRenderEffect
function mountComponent(initialVNode, container, parentComponent) {
    const instance = (initialVNode.component = createComponentInstance(
      initialVNode,
      parentComponent
    ));

    setupComponent(instance);

    setupRenderEffect(instance, initialVNode, container);
}

createComponentInstance

export function createComponentInstance(vnode, parent) {
  const instance = {
    type: vnode.type,
    vnode,
    next: null, // 需要更新的 vnode,用于更新 component 类型的组件
    props: {},
    parent,
    provides: parent ? parent.provides : {}, //  获取 parent 的 provides 作为当前组件的初始化值 这样就可以继承 parent.provides 的属性了
    proxy: null,
    isMounted: false,
    attrs: {}, // 存放 attrs 的数据
    slots: {}, // 存放插槽的数据
    ctx: {}, // context 对象
    setupState: {}, // 存储 setup 的返回值
    emit: () => {},
  };

  instance.ctx = {
    _: instance,
  };

  instance.emit = emit.bind(null, instance) as any;

  return instance;
}

setupComponent

  • initProps、initSlots是把vnode上的props和 children挂载到instance
  • setupStatefulComponent中主要是 执行setup函数,如果返回函数表明是一个渲染函数render,如果是对象则为响应式数据;
  • 执行了两次setCurrentInstance,这让我们在setup里面执行getCurrentInstance函数可以获取instance对象
  • finishComponentSetup对instance.render函数进行处理,如果没有render方法,需要对template进行编译,生成render方法,这点在后面 8、compile章节详细解析
export function setupComponent(instance) {
  const { props, children } = instance.vnode;
  initProps(instance, props);
  initSlots(instance, children);

  setupStatefulComponent(instance);
}

function setupStatefulComponent(instance) {
  instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);
  const Component = instance.type;
  const { setup } = Component;
  if (setup) {
    setCurrentInstance(instance);
    
    const setupContext = createSetupContext(instance);
    const setupResult =
      setup && setup(shallowReadonly(instance.props), setupContext);
    setCurrentInstance(null);

    handleSetupResult(instance, setupResult);
  } else {
    finishComponentSetup(instance);
  }
}

function handleSetupResult(instance, setupResult) {
  if (typeof setupResult === "function") {
    instance.render = setupResult;
  } else if (typeof setupResult === "object") {
    instance.setupState = proxyRefs(setupResult);
  }

  finishComponentSetup(instance);
}

setupRenderEffect

  • instance.update 赋值为effect函数执行结果
  • 这也是组件更新执行的函数
  • componentUpdateFn方法中主要也是深度优先遍历调用patch方法,最终instance的type不为组件,便会进行dom的生成插入,见processElement
function setupRenderEffect(instance, initialVNode, container) {
    function componentUpdateFn() {...}
    instance.update = effect(componentUpdateFn, {
      scheduler: () => {
        queueJob(instance.update);
      },
    });
  }
  
// 组件的更新
function updateComponent(n1, n2, container) {
    ...
     instance.update();
}

processElement

它也是分为初次渲染和更新

  function processElement(n1, n2, container, anchor, parentComponent) {
    if (!n1) {
      mountElement(n2, container, anchor);
    } else {
      updateElement(n1, n2, container, anchor, parentComponent);
    }
  }

mountElement

  • 创建一个element节点
  • 遍历children去调用patch方法
  • 处理节点属性
  • 插入节点
  function mountElement(vnode, container, anchor) {
    const { shapeFlag, props } = vnode;
    const el = (vnode.el = hostCreateElement(vnode.type));
    mountChildren(vnode.children, el);
    for (const key in props) {
        const nextVal = props[key];
        hostPatchProp(el, key, null, nextVal);
    }
    // mountChildren后插入,使得子组件的节点先于父节点
    hostInsert(el, container, anchor);
 }
 function mountChildren(children, container) {
    children.forEach((VNodeChild) => {
      patch(null, VNodeChild, container);
    });
  }

updateElement

  function updateElement(n1, n2, container, anchor, parentComponent) {
    const oldProps = (n1 && n1.props) || {};
    const newProps = n2.props || {};
    const el = (n2.el = n1.el);

    patchProps(el, oldProps, newProps);
    patchChildren(n1, n2, el, anchor, parentComponent);
 }

3、响应式视图 effect

上面组件渲染流程讲到最后执行到如下

 instance.update = effect(componentUpdateFn, {
      scheduler: () => {
        queueJob(instance.update);
      },
});
function componentUpdateFn() {
  if (!instance.isMounted) {
    const proxyToUse = instance.proxy;
    const subTree = (instance.subTree = normalizeVNode(
      instance.render.call(proxyToUse, proxyToUse)
    ));

    console.log(`${instance.type.name}:触发 beforeMount hook`);

    patch(null, subTree, container, null, instance);

    initialVNode.el = subTree.el;

    console.log(`${instance.type.name}:触发 mounted hook`);
    instance.isMounted = true;
  } else {
    const { next, vnode } = instance;
    if (next) {
      next.el = vnode.el;
      updateComponentPreRender(instance, next);
    }
    const proxyToUse = instance.proxy;
    const nextTree = normalizeVNode(
      instance.render.call(proxyToUse, proxyToUse)
    );
    const prevTree = instance.subTree;
    instance.subTree = nextTree;

    // 触发 beforeUpdated hook
    patch(prevTree, nextTree, prevTree.el, null, instance);
    // 触发 updated hook
  }
}

effect代码如下

  • 实例化ReactiveEffect,主要有deps属性,存放响应式数据,这里称为ref对象
  • run方法用来执行传入方法,在方法fn前后分别给activeEffect赋值,这可以让fn执行过程中依赖收集,即对effect的收集(类似vue2中的watcher)
  • 上面effect第一个参数为componentUpdateFn方法,主要逻辑为
    • 执行instance.render方法,获取vnode,在这个过程中会get响应式数据ref
    • 深度遍历vnode执行patch方法,对子节点/组件解析
export function effect(fn, options = {}) {
  const _effect = new ReactiveEffect(fn);
  extend(_effect, options);
  _effect.run();

  const runner: any = _effect.run.bind(_effect);
  runner.effect = _effect;
  return runner;
}

export class ReactiveEffect {
  active = true;
  deps = [];
  public onStop?: () => void;
  constructor(public fn, public scheduler?) {
  }

  run() {
    if (!this.active) {
      return this.fn();
    }

    shouldTrack = true;
    activeEffect = this as any;
    const result = this.fn();
    shouldTrack = false;
    activeEffect = undefined;

    return result;
  }

  stop() {
    if (this.active) {
      cleanupEffect(this);
      if (this.onStop) {
        this.onStop();
      }
      this.active = false;
    }
  }
}

4、依赖收集 ref

实例化RefImpl,dep为一个Set数组

export function ref(value) {
  return createRef(value);
}
export class RefImpl {
  private _rawValue: any;
  private _value: any;
  public dep;
  public __v_isRef = true;

  constructor(value) {
    this._rawValue = value;
    this._value = convert(value);
    this.dep = createDep();
  }

  get value() {
    trackRefValue(this);
    return this._value;
  }

  set value(newValue) {
      triggerRefValue(this);
  }
}

在获取该value时执行trackRefValue收集effect到dep中;

export function trackRefValue(ref) {
  if (isTracking()) {
    trackEffects(ref.dep);
  }
}
export function trackEffects(dep) {
  if (!dep.has(activeEffect)) {
    dep.add(activeEffect);
    (activeEffect as any).deps.push(dep);
  }
}

改变值触发set函数,执行triggerRefValue 通知effect执行更新逻辑

export function triggerRefValue(ref) {
  triggerEffects(ref.dep);
}
export function triggerEffects(dep) {
  for (const effect of dep) {
    if (effect.scheduler) {
      effect.scheduler();
    } else {
      effect.run();
    }
  }
}

5、微任务 queueJob

在effect的更新函数scheduler使用了queueJob,

instance.update = effect(componentUpdateFn, {
      scheduler: () => {
        queueJob(instance.update);
      },
});

// packages\runtime-core\src\scheduler.ts
const queue: any[] = [];
const p = Promise.resolve();
let isFlushPending = false;
export function nextTick(fn) {
  return fn ? p.then(fn) : p;
}
export function queueJob(job) {
  if (!queue.includes(job)) {
    queue.push(job);
    queueFlush();
  }
}
function queueFlush() {
  if (isFlushPending) return;
  isFlushPending = true;
  nextTick(flushJobs);
}
function flushJobs() {
  isFlushPending = false;
  let job;
  while ((job = queue.shift())) {
    if (job) {
      job();
    }
  }
}

6、reactive

  • 所有的reactive对象都放reactiveMap存放,下次直接拿取
export const reactiveMap = new WeakMap();
export function reactive(target) {
  return createReactiveObject(target, reactiveMap, mutableHandlers);
}

function createReactiveObject(target, proxyMap, mutableHandlers) {
  const existingProxy = proxyMap.get(target);
  if (existingProxy) {
    return existingProxy;
  }

  const proxy = new Proxy(target, mutableHandlers);
  proxyMap.set(target, proxy);
  return proxy;
}

export const mutableHandlers = {
  get: (target, key, receiver)=>{
    track(target, "get", key)
    return  Reflect.get(target, key, receiver)
  },
  set: (target, key, receiver)=>{
    trigger(target, "set", key)
    return Reflect.set(target, key, value, receiver)
  },
};

  • reactive的每个属性值进行响应式处理,都有一个dep属性进行effect收集,在属性值变化触发effect的更新
const targetMap = new WeakMap()
export function track(target, type, key) {
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }
  let dep = depsMap.get(key);
  if (!dep) {
    dep = createDep();
    depsMap.set(key, dep);
  }
  trackEffects(dep);
}
export function trigger(target, type, key) {
  let depsMap = targetMap.get(target);
  const dep = depsMap.get(key);
  triggerEffects(dep);
}

7、computed

一般用法如下

const plusOne = computed(() => count.value + 1)
  • 它既是一个响应式数据,所有具有dep属性,在其他effect调用它的时候收集该effect到dep;
  • 它又是一个监听响应式数据的effect,其值依赖其他响应式数据ref。构造函数定义effect属性,在执行getter的时候,将它添加进依赖的ref 的dep里面;在依赖的 响应式数据ref 变化时,通知该computed effect;
  • 执行triggerRefValue来通知 依赖该computed的effect进行更新。

其过程如下:

  • render 函数调用 plusOne,添加进plusOne的dep中;
  • count.value变化,通知 plusOne effect;
  • plusOne执行其dep的更新;
export function computed(getter) {
  return new ComputedRefImpl(getter);
}
export class ComputedRefImpl {
  public dep: any;
  public effect: ReactiveEffect;

  private _dirty: boolean;
  private _value

  constructor(getter) {
    this._dirty = true;
    this.dep = createDep();
    this.effect = new ReactiveEffect(getter, () => {
      if (this._dirty) return;
      this._dirty = true;
      triggerRefValue(this);
    });
  }

  get value() {
    trackRefValue(this);
    if (this._dirty) {
      this._dirty = false;
      this._value = this.effect.run();
    }
    return this._value;
  }
}

8、compile

  • new Function使用方式
var test = new Function('arg','console.log(arg+1)');
 //其等价于
var test = function(arg) { console.log(arg + 1); }

 //则下面等价
var render = new Function('Vue',code)(runtimeDom) 
var render = (function(Vue){ eval(code)})(runtimeDom) 
  • 编译前后对比
Component.render=compile(Component.template)
 //编译前
template: `<p>{{msg}}</p>`

 //compile编译后返回
code = `
  const {toDisplayString:_s , createElementVnode:_c}  = Vue
  return function render(_ctx){
      return _c('p',null,_s(_ctx.msg)
  }
`
new Function('Vue',code)(runtimeDom) 

编译入口开始

在入口处,会执行registerRuntimeCompiler函数让compile赋值为compileToFunction

function compileToFunction(template, options = {}) {
  const { code } = baseCompile(template, options);
  const render = new Function("Vue", code)(runtimeDom);

  return render;
}

registerRuntimeCompiler(compileToFunction);

Component.render = compile(template)

前面 setupComponent-> finishComponentSetup 对instance.render函数进行处理,如果没有render方法,需要对template进行编译,生成render方法,这里compile方法即为compileToFunction,可看出执行baseCompile方法

function finishComponentSetup(instance) {
  const Component = instance.type;

  if (!instance.render) {
    if (compile && !Component.render) {
      if (Component.template) {
        const template = Component.template;
        Component.render = compile(template);
      }
    }

    instance.render = Component.render;
  }
}

baseCompile

  • baseParse 生成ast
  • transform 对Element、Text、Expression进行处理
  • generate:生成code,即render函数
export function baseCompile(template, options) {
  const ast = baseParse(template);

  transform(
    ast,
    Object.assign(options, {
      nodeTransforms: [transformElement, transformText, transformExpression],
    })
  );

  return generate(ast);
}

欢迎关注我的前端自检清单,我和你一起成长