应用实例
每个 Vue 应用都是通过createApp 函数创建一个新的 应用实例,我们传入 createApp 的对象实际上是一个组件,每个应用都需要一个“根组件”,其他组件将作为其子组件。
import { createApp } from 'vue'
const app = createApp({
data() {
return {
count: 0
}
}
})
app.mount('#app')
挂载应用
createApp 会返回一个拥有mount方法的实例,应用实例必须在调用了 mount 方法后才会渲染出来。
mount挂载组件时需要用根组件生成虚拟节点vnode,然后使用render方法开始渲染。
function createApp(rootComponent) {
const app = {
_component: rootComponent,
mount(rootContainer) {
const vnode = createVNode(rootComponent);
render(vnode, rootContainer);
}
};
return app;
}
虚拟DOM渲染成真实DOM - patch
虚拟DOM渲染成真实DOM是由patch方法完成的,patch会对比新旧vnode并更新真实DOM。
patch 需要分情况处理:
- 文本节点,通过DOM API更新
- 创建文本节点 createTextNode
- 设置节点值 nodeValue
- 插入节点 insertBefore
- fragment 类型,只渲染子节点
- 递归patch子节点 mountChildren
- element 类型 processElement,节点,通过DOM API更新
- 首次渲染 mountElement
- 创建元素 createElement
- 设置文本 textContent
- 递归patch子节点 mountChildren
- 设置属性 patchProp
- 插入元素 insertBefore
- 更新 updateElement
- patchProp,遍历新属性找到新增或变化的属性、遍历旧属性找到需移除的属性
- 增删属性 setAttribute/removeAttribute
- on开头的属性需要增删事件处理器 addEventListener/removeEventListener
- patchChildren
- patchProp,遍历新属性找到新增或变化的属性、遍历旧属性找到需移除的属性
- 首次渲染 mountElement
- 组件类型 processComponent,渲染组件
- createComponentInstance 创建组件实例
- setupComponent 加工组件数据
- 初始化props、slots
- 调用 setup 处理返回结果
- setupRenderEffect 建立响应式渲染 ReactiveEffect
- 建立组件更新函数 componentUpdateFn
- 调用渲染函数 render 生成子节点,递归patch子节点
- 收集渲染函数的响应式依赖,依赖改变时用微任务执行更新函数
- 建立组件更新函数 componentUpdateFn
patch 代码描述:
const render = (vnode, container) => {
patch(null, vnode, container);
};
function patch(n1, n2, container = null, anchor = null, parentComponent = null) {
// 基于 n2 的类型来判断
// 因为 n2 是新的 vnode
const { type, shapeFlag } = n2;
switch (type) {
case Text:
processText(n1, n2, container);
break;
// 其中还有几个类型比如: static fragment comment
case Fragment:
processFragment(n1, n2, container);
break;
default:
// 这里就基于 shapeFlag 来处理
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(n1, n2, container, anchor, parentComponent);
} else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
processComponent(n1, n2, container, parentComponent);
}
}
}
function processComponent(n1, n2, container, parentComponent) {
if (!n1) {
mountComponent(n2, container, parentComponent);
} else {
updateComponent(n1, n2);
}
}
渲染组件
组件渲染分为首次渲染和更新,分别由mountComponent、updateComponent实现。
- 首次渲染组件
- createComponentInstance 创建组件实例
- type 创建实例时被赋值为vnode.type, 组件对象会在创建虚拟dom时被存入vnode.type
- proxy
- setupComponent 加工组件数据
- 初始化props,赋值实例 props 属性
- 初始化slots,赋值实例 slots 属性,将vnode.children 存到instance.slots
- 初始化组件数据setupStatefulComponent,调用 setup 处理返回结果
- 赋值实例的 proxy 属性,代理instance.ctx
- setupRenderEffect 建立响应式渲染 ReactiveEffect
- 建立组件更新函数 componentUpdateFn
- 赋值instance.update
- createComponentInstance 创建组件实例
- 组件更新
- 判断是否需要更新
- 若是则调用instance.update更新
- 判断是否需要更新
mountComponent 代码描述:
function mountComponent(initialVNode, container, parentComponent) {
const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent));
setupComponent(instance);
setupRenderEffect(instance, initialVNode, container);
}
function createComponentInstance(vnode, parent) {
const instance = {
type: vnode.type,
vnode,
props: {},
attrs: {},
slots: {},
parent,
provides: parent ? parent.provides : {},
// ...
setupState: {},
};
instance.ctx = {
_: instance
};
instance.emit = emit.bind(null, instance);
return instance;
}
function updateComponent(n1, n2, container) {
const instance = (n2.component = n1.component);
if (shouldUpdateComponent(n1, n2)) {
instance.next = n2;
instance.update();
} else {
n2.component = n1.component;
n2.el = n1.el;
instance.vnode = n2;
}
}
建立响应式渲染 setupRenderEffect
组件数据改变后需要调用组件update方法重新渲染,需要用响应式副作用建立响应式渲染,即用effect包裹更新函数,当更新函数内的依赖改变时重新调用更新函数。
建立响应式渲染:
- 建立组件更新函数 componentUpdateFn
- 调用渲染函数 render 生成子节点,递归patch子节点
- 建立响应式更新 instance.update,收集渲染函数的响应式依赖,依赖改变时用微任务执行更新函数
setupRenderEffect 代码描述:
function setupRenderEffect(instance, initialVNode, container) {
function componentUpdateFn() {
if (!instance.isMounted) {
const proxyToUse = instance.proxy;
const subTree = (instance.subTree = normalizeVNode(instance.render.call(proxyToUse, proxyToUse)));
patch(null, subTree, container, null, instance);
initialVNode.el = subTree.el;
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;
patch(prevTree, nextTree, prevTree.el, null, instance);
}
}
instance.update = effect(componentUpdateFn, {
scheduler: () => {
queueJob(instance.update);
}
});
}
function updateComponentPreRender(instance, nextVNode) {
nextVNode.component = instance;
instance.vnode = nextVNode;
instance.next = null;
const { props } = nextVNode;
instance.props = props;
}
Template 中 ref 的解包
在模板template或者渲染函数render中使用ref变量时会自动解包,可以直接使用ref而不需要通过.value访问ref的值。
ref 的解包实现流程:
- 调用setup获取setup返回的数据
- 对setupState做代理,在代理中对属性进行unRef解包
function mountComponent(initialVNode, container, parentComponent) {
const instance = createComponentInstance(initialVNode, parentComponent);
// ...
const { setup } = instance.type;
const setupContext = {/* ... */};
const setupResult = setup && setup(instance.props, setupContext);
instance.setupState = proxyRefs(setupResult);
instance.render.call(instance.setupState, instance.setupState);
}
function proxyRefs(objectWithRefs) {
return new Proxy(objectWithRefs, {
get(target, key, receiver) {
return unRef(Reflect.get(target, key, receiver));
},
set(target, key, value, receiver) {
const oldValue = target[key];
if (isRef(oldValue) && !isRef(value)) {
return (target[key].value = value);
} else {
return Reflect.set(target, key, value, receiver);
}
}
});
}
Veux4 Store 响应式的实现
- Vuex 是以插件的形式在 Vue 中使用的,在 createApp 时调用 install 安装
- Store 类的 install,通过 app.provide 和 config.globalProperties.$store 提供 store 实例
- Vuex4 中的 state 是通过 reactive API 去创建的响应式数据
- useStore 用 inject 获取 provide 时存入的 store
import { inject } from 'vue';
class Store {
constructor(options = {}) {
this._committing = false;
this._actions = Object.create(null);
this._actionSubscribers = [];
this._mutations = Object.create(null);
this._wrappedGetters = Object.create(null);
this._modules = new ModuleCollection(options);
this._modulesNamespaceMap = Object.create(null);
this._subscribers = [];
this._makeLocalGettersCache = Object.create(null);
// ...
const state = this._modules.root.state;
resetStoreState(this, state);
}
install(app, injectKey) {
app.provide(injectKey || storeKey, this);
app.config.globalProperties.$store = this;
}
}
function resetStoreState(store, state, hot) {
store._state = reactive({
data: state
});
}
export const storeKey = 'store';
export function useStore(key = null) {
return inject(key !== null ? key : storeKey);
}