Vue3 核心模块源码解析(中)
创始人
2024-05-29 07:05:30
0

【Vue3 核心模块源码解析(上)】讲到了 Vue2 与 Vue3的一些区别,Vue3 新特性的使用,以及略微带了一点源码。那么这篇文章就要从Vue3 模块源码解析Vue3 执行逻辑解析这两个方面去给大家剖析 Vue3 的深层次,一起学习起来吧!

这里还是想多说一点,源码几句话很难去解释清除,下面的核心代码都有写注释,大家按照思路一起走下去吧,本文末尾会有整体的流程图!

文章目录

    • Vue3 核心源码解析
      • 1. compiler-core
        • 1.1 目录结构
        • 1.2 compile 逻辑
      • 2. reactivity
        • 2.1 目录结构
        • 2.2 reactivity 逻辑
      • 3. runtime-core
        • 3.1 目录结构
        • 3.2 runtime 核心逻辑
      • 4. runtime-dom
        • 4.1 主要功能
      • 5. shared
        • 5.1 代码示例
    • Vue3 执行逻辑解析
      • init —— 组件初始化

Vue3 核心源码解析

为什么要去看源码?可能很多人感觉你在装X,事实并不是这样,就像我们在 【上】中讲到 ref 与 reactive 都可以生成响应式数据,为什么更推荐用 reactive 来代替 ref 生成深层次响应式数据结构呢?读读源码,从宏观的设计角度去考虑,可以更快的加速我们的成长!

在这里插入图片描述

本篇文章主要从 packages -> compiler-core/reactivity/runtime-core 这三个阶段去学习,Vue3是如何实现响应式的Vue3怎么样实现AST模型Vue3如何在运行时做一些Diff
开头提到的 MonoRepo 的包管理方式也在 packages 的包中提现出来了,一个多包的管理方式

在这里插入图片描述

1. compiler-core

Vue3 的编译核心,作用就是将字符串转换成 抽象语法树AST(Abstract Syntax Tree)

1.1 目录结构

  |-src|  |—— ast.ts // ts类型定义,比如type,enum,interface等|  |—— codegen.ts // 将生成的ast转换成render字符串|  |—— compile.ts // compile统一执行逻辑,有一个 baseCompile ,用来编译模板文件的|  |—— index.ts // 入口文件|  |—— parse.ts // 将模板字符串转换成 AST|  |—— runtimeHelpers.ts // 生成code的时候的定义常量对应关系|  |—— transform.ts // 处理 AST 中的 vue 特有语法|  |—— utils.ts // 工具类|  ||  |—— transforms // 需要转换的类型|			transformElement.ts|			transformExpression .ts|			transformText.ts|||——// 测试用例tests|—— codegen.spec.ts|—— parse.spec.ts|—— transform.spec.ts||—— snapshotscodegen.spec.ts.snap

1.2 compile 逻辑

1.2.1 compile.ts

为了方便阅读与理解,把 TS 的类型部分环境判断断言等相关内容省略了

compile 这个包主要是实现了什么能力呢?下面就是 compiler-core 核心

  1. 把用户输入的内容做了 AST 的转换,
  2. 转译成 Vue 能够识别的语言或者说 Vue 能够识别的语法
import { generate } from './codegen';
import { baseParse } from './parse';
import { transform } from './transform';
import { transformExpression } from './transforms/transformExpression';
import { transformElement } from './transforms/transformElement';
import { transformText } from './transforms/transformText';export function baseCompile(template, options){// 1. 先把 template 也就是字符串 parse 成 astconst ast = baseParse(tempalte);// 2. 给 ast 加点料 --> 做了一些处理transform(ast,Object.assign(options,{ nodeTransforms: [transformElement, transformText, transformExpression] }))// 3. 生成 render 函数return generate(ast)
}

1.2.2 parse.ts

AST 的逻辑 简而言之就是一开始我们是一个模板的语言,之后通过我们的一套解析规则,最后可以生成一个 Tree 或者说是一个对象,对象里面就是对应我们的标签属性,比如type、value、等,可以这么简单的理解AST。
parse.ts 主要就是进行一些 AST 的逻辑处理

import { ElementTypes, NodeTypes ] from "./ast";const enum TagType {start,End ,
}export function baseParse(content: string) {// 创建上下文const context = createParserContext(content);return createRoot(parseChildren(context,[]));
}function createParserContext(content) {console.log("创建 parseContext");return {// 真实源码会有很多参数,这里省略了source: content  }
}function parseChildren(context, ancestors) {console.log('开始解析 children');const nodes: any[] = []while (!isEnd(context, ancestors)) {const s = context.sourcelet node = undefinedif (startsWith(s, "{{")) {// '{{'// 看看如果是 {{ 开头的话,那么就是一个插值,那么去解析他node = parseInterpolation(context, mode)} else if (s[0] === '<') {if (s[1] === '/') {// https://html.spec.whatwg.org/multipage/parsing.html#end-tag-open-state// 这里属于 edge case 可以不用关心// 处理结束标签if (/[a-z]/i.test(s[2])) {// 匹配 
// 需要改变 context.source 的值 -> 也就是需要移动光标parseTag(context, TagType.End)// 结束标签就以为这都已经处理完了,所以就可以跳出本地循环了continue}} else if (/[a-z]/i.test(s[1])) {node = parseElement(context, ancestors)}}if (!node) {node = parseText(context, mode)}nodes.push(node)}return nodes }function parseInterpolation(context: any,): InterpolationNode | undefined {// 1.先获取到结束的 index// 2.通过 closeIndex - startIndex 获取到内容的长度 contextLength// 3.通过slice 截取内容// }} 是插值的关闭// 优化点是从 {{ 后面搜索即可const openDelimiters = "{{";const closeDelimiters = "}}";const closeIndex = context.source.indexOf(closeDelimiters, openDelimiters.length)// TODO 需要报错if (closeIndex === -1) {// emitError(context, ErrorCodes.X_MISSING_INTERPOLATION_END)return undefined}// 让代码前进两个长度 可以把 {{ 干掉advanceBy(context, open.length)const rawContentLength = closeIndex - openDelimiters.lengthconst rawContent = context.source.slice(0, rawContentLength)const preTrimContent = parseTextData(context, rawContentLength)const content = preTrimContent.trim()// 最后让代码前进两个长度 可以把 }} 干掉advanceBy(context, close.length)return {type: NodeTypes.INTERPOLATION,content: {type: NodeTypes.SIMPLE_EXPRESSION,content,},} }function parseTag(context: any, type: TagType): any {// 发现如果不是 > 的话,那么就把字符都收集起来 ->div// 正则 const match: any = /^<\/?([a-z][\r\nt f />]*)/i.exec(context.source);const tag = match[1];// 移动光标// advanceBy(context, 1);if (type === TagType.End) return;let tagType = ElementTypes.ELEMENT;return {type: NodeTypes.ELEMENT,tag,tagType,} }function parseElement(context, ancestors) {// 应该如何解析 tag 呢//
// 先解析开始 tagconst element = parseTag(context, TagType.Start);ancestors.push(element);const children = parseChildren(context, ancestors);ancestors.pop();// 解析 end tag 是为了检测语法是不是正确的// 检测是不是和 start tag 一致if (startsWithEndTagOpen(context.source, element.tag)) {parseTag(context, TagType.End);} else {throw new Error(`缺失结束标签:${element.tag}`);}element.children = children;return element; }function createRoot(children) {return {type: NodeTypes.ROOT,children,helpers: [],} }function startswith(source: string, searchString: string): boolean {return source.startswith(searchString); }function isEnd(context: any, ancestors) {//检测标签的节点// 如果是结束标签的话,需要看看之前有没有开始标签,如果有的话,那么也应该结束// 这里的一个 edge case 是
// 像这种情况下,其实就应该报错const s = context.source;if (context.source.startswith('// 从后面往前面查// 因为便签如果存在的话 应该是 ancestors 最后一个元素for (let i = ancestors.length - 1; i >= 0; --i) {if (startswithEndTagOpen(s, ancestors[i].tag)) {return true;}}}// 看看 context.source 还有没有值return !context.source; }function startswithEndTagOpen(source: string, tag: string) {// 1.头部 是不是以

1.2.3 transform.ts

transform 方法主要做了一下几点事

  1. 创建 context
  2. 递归遍历 node, 针对不同的类型(NodeTypes)做不同的处理
  3. createRootCodegen --> 创建根节点

helper 有点类似于 GC 当中的 引用计数 算法很像,这里维护的是一个 Map 对象,比如在 unMount 会判断我们当前 count 是否为0,为0时则删除,也是做垃圾回收用的;

function createTransformContext(root, options): any {const context = {root,nodeTransforms: options.nodeTransforms || [],helpers: new Map(),helper(name) {// 这里会收集调用的次数// 收集次数是为了给删除做处理的,(当只有 count 为0的时候才需要真的删除掉)// helpers 数据会在后续生成代码的时候用到const count = context.helpers.get(name) || 0;context.helpers.set(name, count + 1);return context;},};
}function createRootCodegen(root: any, context: any) {const { children } = root;// 只支持有一个根节点// 并且还是一个 single text nodeconst child = children[0];// 如果是 element 类型的话,那么我们需要把它的 codegenNode 赋值给 root// root 其实是个空的什么数据都没有的节点// 所以这里需要额外的处理 codegenNode// codegenNode 的目的是专门为了 codegen 准备的 为的就是和 ast 的 node 分离开if (child.type === NodeTypes.ELEMENT && child.codegenNode) {const codegenNode = child.codegenNode;root.codegenNode = codegenNode;} else {root.codegenNode = child;}
}

1.2.4 generate.ts

import { isString } from '@mini-vue/shared';
import { NodeTypes } from './ast';
import { CREATE_ELEMENT_VNODE, 
helperNameMap,TO_DISPLAY_STRING } from './runtimeHelpers'; export function generate(ast, options = {}) {// 先生成 contextconst context = createCodegenContext(ast, options);const { push, mode } = context;//1.先生成 preambleContextif (mode === "module") {genModulePreamble(ast, context);} else {genFunctionPreamble(ast, context);}const functionName = "render";const args = ["_ctx"];// _ctx,aaa,bbb,ccc// 需要把 args 处理成 上面的 stringconst signature = args.join(",");push(`function ${functionName}(${signature}) {`);// 这里需要生成具体的代码内容// 开始生成 vNode tree 表达式push("return ");genNode(ast.codegenNode, context);push("}");return {code: context.code,};
}

2. reactivity

reactivity 实现了什么样的逻辑呢?
可以看下面 index.ts 的引入,基本上就是我们在 Vue3 核心模块源码解析(上)
中讲到的实现响应式的 内容reactive、ref、isRef 、effect等;

export {reactive,readonly,shal1owReadonly,isReadonly,isReactive,isProxy,
} from "./reactive";export { ref, proxyRefs, unRef, isRef } from "./ref";
export { effect, stop, ReactiveEffect } from "./effect";
export { computed } from "./computed";

2.1 目录结构

  |-src|  |—— index.ts // 所有响应式 API 的暴露,比如ref、unRef、isRef、effect 等|  |—— reactive.ts // reactive 响应式 的实现|  |—— ref.ts // ref 响应式 的实现|  |—— dep.ts // |  |—— effect.ts // |  |—— baseHandler.ts // |  |—— computed.ts // |  ||  ||||——// 测试用例tests|—— xxx.spec.ts|—— xxxx.spec.ts // 对应src目录下

2.2 reactivity 逻辑

2.2.1 reactive.ts

Vue3 的响应式基本上都是通过 weakMap 来实现的,最核心的原因是 weakMap 可以使用对象的方式作为键,其次就是弱引用更好的支持垃圾回收。

import {mutableHandlers,readonlyHandlers,shallowReadonlyHandlers,
} from "./baseHandlers";
export const reactiveMap = new WeakMap();
export const readonlyMap = new WeakMap();
export const shallowReadonlyMap = new WeakMap();
export const enum ReactiveFlags {IS_REACTIVE = "_v_isReactive",IS_READONLY = "_v_isReadonly",RAW = "_v_raw",
}
export function reactive(target) {return createReactiveobject(target, reactiveMap, mutableHandlers);
}
export function readonly(target) {return createReactiveobject(target, readonlyMap, readonlyHandlers);
}
export function shallowReadonly(target) {return createReactiveObject(target,shallowReadonlyMap,shallowReadonlyHandlers);
}
export function isProxy(value) {return isReactive(value) || isReadonly(value);
}
export function isReadonly(value) {return !!value[ReactiveFlags.IS_READONLY];
}export function isReactive(value) {// 如果 value 是 proxy 的话// 会触发 get 操作,而在 createGetter 里面会判断// 如果 value 是普通对象的话// 那么会返回 undefined,那么就需要转换成布尔值return !!value[ReactiveFlags.IS_REACTIVE];
}export function toRaw(value) {// 如果 value 是 proxy 的话那么直接返回就可以了// 因为会触发 createGetter 内的逻辑// 如果 value 是普通对象的话,// 我们就应该返回普通对象// 只要不是 proxy ,只要是得到了 undefined 的话,那么就一定是普通对象// TODO 这里和源码里面实现的不一样,不确定后面会不会有问题if (!value[ReactiveFlags.RAW]) {return value;}
}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;
}

2.2.2 ref.ts

ref 的大概实现逻辑

import { trackEffects, triggerEffects, isTracking } from "/effect";
import { createDep } from "./dep";
import { isObject, hasChanged } from "@mini-vue/shared";
import { reactive } from "./reactive";export class RefImpl {private _rawValue: any;private _value: any;public dep;public __v__isref = true;constructor(value) {this.rawValue = value;// 看看value 是不是一个对象,如果是一个对象的话// 那么需要用 reactive 包裹一下this.value = convert(value);// 这里会在dep.ts 里面单独声明// 其实就是一个 new Set 的结构this.dep = createDep();}get value() {// 收集依赖// 这里类似于 Vue2 的 watcher 依赖收集// dep.add() 不过相比 Vue2 的数组,这里做了 new Set 的优化// 收集依赖时会 先判断是否收集过trackRefValue(this);return this._value;}set value(newValue) {// 当新的值不等于老的值的话// 那么才需要触发依赖if (hasChanged(newValue, this._rawValue)) {// 更新值this._value = convert(newValue);this._rawValue = newValue;// 执行收集到的所有的依赖 effect 的 run 方法// 类似于 Vue2 中的 Dep.notify()// 内部实际上是用 scheduler 可以让用户自己选择调用时机// 在 runtime-core 中,就是使用了 scheduler 实现在 next ticker 中调用的逻辑triggerRefValue(newValue);}}
}export function ref(value) {return createRef(value);
}
function convert(value) {// 这里 isobject 非常简单,就是用的 Object.isreturn isobject(value) ? reactive(value) : value;
}function createRef(value) {const refImpl = new RefImpl(value);return refImpl;
}
export function triggerRefValue(ref) {triggerEffects(ref.dep);
}
export function trackRefValue(ref) {if (isTracking()) {trackEffects(ref.dep);}
}// 这里没有处理 objectwithRefs 是 reactive 类型的时候
// TODO reactive 里面如果有 ref 类型的 key 的话, 那么也是不需要调用 ref.value 的
// (but 这个逻辑在 reactive 里面没有实现)
export function proxyRefs(objectwithRefs) {return new Proxy(objectwithRefs, shallowUnwrapHandlers);
}
// 把 ref 里面的值拿到
export function unRef(ref) {return isRef(ref) ? ref.value : ref;
}
export function isRef(value) {return !!value.__v__isRef;
}

2.2.3 baseHandler.ts

baseHandler 对响应式的处理,get set

问题:为什么是 readonly 的时候不做依赖收集呢?
readonly 的话,是不可以被 set 的,那不可以被 set 就意味着不会触发 trigger,所以就没有收集依赖的必要了

const get = createGetter();
const set = createSetter();
const readonlyGet = createGetter(true);
const shallowReadonlyGet = createGetter(true, true);function createGetter(isReadonly = false, shallow = false) {return function get(target: Target, key: string | symbol, receiver: object) {if (key === ReactiveFlags.IS_REACTIVE) {return !isReadonly;} else if (key === ReactiveFlags.IS_READONLY) {return isReadonly;} else if (key === ReactiveFlags.IS_SHALLOW) {return shallow;} else if (key === ReactiveFlags.RAW &&receiver ===(isReadonly? shallow? shallowReadonlyMap: readonlyMap: shallow? shallowReactiveMap: reactiveMap).get(target)) {return target;}const targetIsArray = isArray(target);if (!isReadonly) {if (targetIsArray && hasOwn(arrayInstrumentations, key)) {return Reflect.get(arrayInstrumentations, key, receiver);}if (key === "hasOwnProperty") {return hasOwnProperty;}}const res = Reflect.get(target, key, receiver);if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {return res;}// 问题:为什么是 readonly 的时候不做依赖收集呢?// readonly 的话,是不可以被 set 的,那不可以被 set 就意味着不会触发 trigger// 所以就没有收集依赖的必要了if (!isReadonly) {// 在触发get的时候信息依赖收集track(target, TrackOpTypes.GET, key);}if (shallow) {return res;}if (isRef(res)) {// ref unwrapping - skip unwrap for Array + integer key.return targetIsArray && isIntegerKey(key) ? res : res.value;}if (isObject(res)) {// 把内部所有的是 object 的值都用 reactive 包裹,变成响应式// 如果说这个 res 值是一个对象的话,那么我们需要把获取到的 res 也转换成 reactive// res 等于 target[key]return isReadonly ? readonly(res) : reactive(res);}return res;};
}export const mutableHandlers: ProxyHandler = {get,set,deleteProperty,has,ownKeys
}export const readonlyHandlers: ProxyHandler = {get: readonlyGet,set(target, key) {if (__DEV__) {// readonly 的响应式对象不可以修改warn(`Set operation on key "${String(key)}" failed: target is readonly.`,target)}return true},deleteProperty(target, key) {if (__DEV__) {warn(`Delete operation on key "${String(key)}" failed: target is readonly.`,target)}return true}
}
export const shallowReactiveHandlers = /*#__PURE__*/ extend({},mutableHandlers,{get: shallowGet,set: shallowSet}
)
 

3. runtime-core

runtime-core: 整个 runtime 的核心,runtime-domruntime-test 等都是为runtime-core提供DOM操作的能力

3.1 目录结构

  |-src|  |—— index.ts // 主文件,暴露 Vue 运行时所需要的各种方法、enum、VNode等|  |—— apiCreateApp.ts // 创建根节点 |  |—— vnode.ts // 定义节点的结构并处理这些结构|  |—— component.ts // 组件返回的所有内容,暴露给用户 -> getCurrentInstance|  |—— componentEmits.ts // emit 方法的处理|  |—— componentProps.ts // props 的处理|  |—— componentPublicInstance.ts // 共用的 instance|  |—— componentSlots.ts // 组件插槽处理|  |—— apiInject.ts // inject 方法的处理|  |—— apiWatch.ts // watch 方法的处理|  |—— renderer.ts // 核心点,diff 的初始化及所有的初始化,下一篇中会详细讲解 Diff|  |—— rendererTemplateRef.ts |  |—— hmr.ts |  |—— h.ts  |  |—— hydration.ts|  |—— profiling.ts|  |—— directives.ts|  |—— devtools.ts|  |—— customFormatter.ts|  |—— componentOptions.ts|  |—— compat|  |—— helpers|——// 测试用例tests|—— xxx.spec.ts|—— xxxx.spec.ts // 对应src目录下

3.2 runtime 核心逻辑

runtime-core 代码片段都比较长,此处挑一些精简过的核心

3.2.1 index.ts

export {// corereactive,ref,readonly,// utilitiesunref,proxyRefs,isRef,toRef,toRefs,isProxy,isReactive,isReadonly,isShallow,// advancedcustomRef,triggerRef,shallowRef,shallowReactive,shallowReadonly,markRaw,toRaw,// effecteffect,stop,ReactiveEffect,// effect scopeeffectScope,EffectScope,getCurrentScope,onScopeDispose
} from '@vue/reactivity'
export { computed } from './apiComputed'
export {watch,watchEffect,watchPostEffect,watchSyncEffect
} from './apiWatch'

3.2.2 apiCreateApp.ts

import { createVNode } from "./vnode";export function createAppAPI(render) {return function createApp(rootComponent) {const app = {component: rootComponent,mount(rootContainer) {console.log("基于根组件创建vnode");const vnode = createVNode(rootComponent);console.log("调用 render,基于 vnode 进行开箱");render(vnode, rootContainer);},};return app;};
}

3.2.3 componentEmits.ts

import { camelize, hyphenate, toHandlerKey } from "@mini-vue/shared";export function emit(instance, event: string, ...rawArgs) {// 1.emit 是基于 props 里面的 onXXX 的函数来进行匹配的// 所以我们先从 props 中看看是否有对应的 event handlerconst props = instance.props;// ex: event -> cick 那么这里取的就是 onclick// 让事情变的复杂一点如果是中划线命名的话,需要转换成change-page -> changePage// 需要得到事件名称let handler = props[toHandlerKey(camelize(event))];// 如果上面没有匹配的话 那么在检测一下 event 是不是 kebab-case 类型if (!handler) {handler = props[toHandlerKey(hyphenate(event))];}if (handler) {handler(...rawArgs);}
}

3.2.4 componentProps.ts

export function initProps(instance, rawProps) {console.log("initProps");// TODO// 应该还有 attrs 的概念//attrs// 如果组件声明了 props 的话,那么才可以进入 props 属性内//// 不然的话是需要存储在 attrs 内// 这里暂时直接赋值给 instance.props 即可instance.props = rawProps;
}

3.2.5 component.ts

import { initProps } from "./componentProps";
import { initslots } from "./componentslots";
import { emit } from "./componentEmits";
import { PublicInstanceProxyHandlers } from "./componentPublicInstance";
import { proxyRefs, shallowReadonly } from "@mini-vue/reactivity";export function createComponentInstance(vnode, parent) {const instance = {type: vnode.type,vnode,next: null, // 需要更新的 ynode,用于更新 component 类型的组件props: (]parent,provides: parent ? parent.provides : {}, // 取 parent 的 provides 作为当前组件的初始化值,这样就可以继承parent.proviedisMounted: false,attrs: {}, // 存放 attrs 的数据slots: {}, // 存放插槽的数据ctx: {}, // context 对象setupstate: {}, // 存储 setup 的返回值emit: () => {},};// 在 prod 坏境下的 ctx 只是下面简单的结构// 在 dev 环境下会更复杂instance.ctx = {_: instance,};// 赋值 emit//这里使用 bind 把 instance 进行绑定// 后面用户使用的时候只需要给 event 和参数即可instance.emit = emit.bind(null, instance) as any;return instance;
}// 组件 setup 的初始化
export function setupComponent(instance) {// 1.处理 props// 取出存在 vnode 里面的 propsconst { props, children } = instance.vnode;initProps(instance, props);// 2。处理 slotsinitslots(instance, children);// 源码里面有两种类型的 component// 一种是基于 options 创建的// 还有一种是 function 的// 这里处理的是 options 创建的// 叫做 stateful 类型setupStatefulComponent(instance);
}function setupStatefulComponent(instance) {// todo// 1,先创建代理 proxyconsole.log("创建 proxy");// proxy 对象其实是代理了 instance.ctx 对象// 我们在使用的时候需要使用 instance.proxy 对象// 因为 instance.ctx 在 prod 和 dev 坏境下是不同的instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);// 用户声明的对象就是 instance.type// const Component = {setup(),render()} ....const Component = instance.type;// 2,调用 setup// 调用 setup 的时候传入 propsconst { setup } = Component;if (setup) {// 设置当前 currentInstance 的值// 必须要在调用 setup 之前setCurrentInstance(instance);const setupContext = createSetupContext(instance);// 真实的处理场景里面应该是只在 dev 环境才会把 props 设置为只读的const setupResult =setup && setup(shallowReadonly(instance.props), setupContext);setCurrentInstance(null);// 3。处理 setupResulthandleSetupResult(instance, setupResult);} else {finishComponentsetup(instance);}
}function handleSetupResult(instance, setupResult) {// setup 返回值不一样的话,会有不同的处理// 1.看看 setupResult 是个什么if (typeof setupResult === "function") {// 如果返回的是 function 的话,那么绑定到 render 上// 认为是 render 逻辑// setup()f return  ()=>(h("div")) }instance.render = setupResult;} else if (typeof setupResult === "object") {// 返回的是一个对象的话// 先存到 setupstate 上// 先使用 @vue/reactivity 里面的 proxyRefs//后面我们自己构建// proxyRefs 的作用就是把 setupResult 对象做一层代理// 方便用户直接访问 ref 类型的值// 比如 setupResult 里面有个 count 是个 ref 类型的对象,用户使用的时候就可以直接使用 count 了, 而不需要在count.value// 这里也就是官网里面说到的自动结构 Ref 类型instance.setupState = proxyRefs(setupResult);}finishComponentsetup(instance);
}

4. runtime-dom

runtime-dom 包:Vue 的底层为什么通过 AST 转换,然后可以在上层供我们的Native、H5、小程序(mpvue)使用;
Vue 是通过 Virtual DOM 实现,runtime-dom 我们可以理解为,给我们VDOM提供了具有真实DOM一样的能力,就是,比如:createElement、createApp、createRenderer等等

4.1 主要功能

此处只是列举

  createElement: (tag, isSVG, is, props): Element => {const el = isSVG? doc.createElementNS(svgNS, tag): doc.createElement(tag, is ? { is } : undefined)if (tag === 'select' && props && props.multiple != null) {;(el as HTMLSelectElement).setAttribute('multiple', props.multiple)}return el},

5. shared

shared 包主要会返回一些通用的逻辑,比如: isObject()、isString()、camelize()、isOn()等等,实际上和 utils 没什么区别

  |-src|  |—— index.ts // 核心方法库,类似于 utils|  |—— shapeFlags.ts // enum 类型文件|  |—— toDiaplayString.ts // 通用转换方法|  |

5.1 代码示例

这里面的方法很多,这里只是列举一个

const camelizeRE = /-(\w)/g;
/*** @private*  把中划线命名方式转换成驼峰命名方式*/export const camelize = (str: string): string => {return str.replace(camelizeRE, (_, c) => (c ? c.toupperCase() : ""));
};

Vue3 执行逻辑解析

init —— 组件初始化

调用 patch ,基于 vNode 类型 进行不同类型的组件处理调用 patch ,基于 vNode 类型 进行不同类型的组件处理开始1.创建 App2.进行初始化1.基于 rootComponent 生成 vNode2.进行 render处理 shapeFlag & ShapeFlag.COMPONENT 类型处理 shapeFlag & ShapeFlag.ELEMENT 类型组件初始化组件更新1.创建 component instance 对象2.setup component初始化 props初始化 slot初始化 setup初始化 render 函数3.setupRenderEffect1. 调用 render 函数获取 vnode -- 子组件2. 触发生命周期 beforeMount Hook3. 调用 patch 初始化子组件4. 触发生命周期 mounted Hook检测是否需要更新 对比props提前更新组件 component 的数据,更新props,更新 slots生成最新的 subTree调用 patch 递归处理 subTreeelement 初始化element 更新1. 调用 beforeCreateElement 创建真实 Element2. 处理 children 节点3. 调用 hostPatchProp 设置元素的prop4. 触发beforeMount 钩子5. 渲染 hostInsert 插入真实的 dom 树6. 触发 Mounted 钩子对比 props对比 children 递归遍历所有children 调用 patch

结语:到此 【Vue3 核心模块源码解析(中)】结束,本篇主要是以 繁琐的代码块为主,配合上 init 整体的流程图,分享了精简后大致的源码;
当然,最关键的 Diff 还没有讲到,Vue2、Vue3、React 的DIff 有什么区别,Vue3 中的 Diff是如何升级的;


相关内容

热门资讯

常用商务英语口语   商务英语是以适应职场生活的语言要求为目的,内容涉及到商务活动的方方面面。下面是小编收集的常用商务...
六年级上册英语第一单元练习题   一、根据要求写单词。  1.dry(反义词)__________________  2.writ...
复活节英文怎么说 复活节英文怎么说?复活节的英语翻译是什么?复活节:Easter;"Easter,anniversar...
2008年北京奥运会主题曲 2008年北京奥运会(第29届夏季奥林匹克运动会),2008年8月8日到2008年8月24日在中华人...
英语道歉信 英语道歉信15篇  在日常生活中,道歉信的使用频率越来越高,通过道歉信,我们可以更好地解释事情发生的...
六年级英语专题训练(连词成句... 六年级英语专题训练(连词成句30题)  1. have,playhouse,many,I,toy,i...
上班迟到情况说明英语   每个人都或多或少的迟到过那么几次,因为各种原因,可能生病,可能因为交通堵车,可能是因为天气冷,有...
小学英语教学论文 小学英语教学论文范文  引导语:英语教育一直都是每个家长所器重的,那么有关小学英语教学论文要怎么写呢...
英语口语学习必看的方法技巧 英语口语学习必看的方法技巧如何才能说流利的英语? 说外语时,我们主要应做到四件事:理解、回答、提问、...
四级英语作文选:Birth ... 四级英语作文范文选:Birth controlSince the Chinese Governmen...
金融专业英语面试自我介绍 金融专业英语面试自我介绍3篇  金融专业的学生面试时,面试官要求用英语做自我介绍该怎么说。下面是小编...
我的李老师走了四年级英语日记... 我的李老师走了四年级英语日记带翻译  我上了五个学期的小学却换了六任老师,李老师是带我们班最长的语文...
小学三年级英语日记带翻译捡玉... 小学三年级英语日记带翻译捡玉米  今天,我和妈妈去外婆家,外婆家有刚剥的`玉米棒上带有玉米籽,好大的...
七年级英语优秀教学设计 七年级英语优秀教学设计  作为一位兢兢业业的人民教师,常常要写一份优秀的教学设计,教学设计是把教学原...
我的英语老师作文 我的英语老师作文(通用21篇)  在日常生活或是工作学习中,大家都有写作文的经历,对作文很是熟悉吧,...
英语老师教学经验总结 英语老师教学经验总结(通用19篇)  总结是指社会团体、企业单位和个人对某一阶段的学习、工作或其完成...
初一英语暑假作业答案 初一英语暑假作业答案  英语练习一(基础训练)第一题1.D2.H3.E4.F5.I6.A7.J8.C...
大学生的英语演讲稿 大学生的英语演讲稿范文(精选10篇)  使用正确的写作思路书写演讲稿会更加事半功倍。在现实社会中,越...
VOA美国之音英语学习网址 VOA美国之音英语学习推荐网址 美国之音网站已经成为语言学习最重要的资源站点,在互联网上还有若干网站...
商务英语期末试卷 Part I Term Translation (20%)Section A: Translate ...