一、如何解读?
初级
- 了解 vue2/vue3 语法,了解其语法,理解什么是mvvm
- 官方中文文档
// 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>
中级
高级
自己对着源码看吧 ~
二、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:一些公共函数和常量
流程图如下

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);
}
欢迎关注我的前端自检清单,我和你一起成长