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是如何升级的;


相关内容

热门资讯

我的心爱之物四年级作文【优秀... 我的心爱之物四年级作文 篇一我最心爱的物品是一本名为《小王子》的图书。这本书是我四年级时的生日礼物,...
跳绳比赛四年级优秀作文(经典... 跳绳比赛四年级优秀作文 篇一:我的跳绳比赛经历上周,我们学校举行了一场跳绳比赛,我参加了四年级的比赛...
四年级我画画作文【精彩6篇】 四年级我画画作文 篇一我是一个四年级的小学生,非常喜欢画画。自从我学会拿起画笔,画画已经成为了我生活...
关于蜜蜂的作文四年级【优选6... 关于蜜蜂的作文四年级 篇一蜜蜂是一种非常有趣的昆虫,它们生活在蜂巢中,有着重要的作用。今天,我就来给...
关于危急时刻的四年级作文50... 关于危急时刻的四年级作文500字 篇一危急时刻在我们的生活中,我们常常会遇到一些危急时刻,这些时刻需...
我最敬佩的一位名人四年级作文... 我最敬佩的一位名人四年级作文 篇一我最敬佩的一位名人是李彦宏。李彦宏是中国著名的企业家,同时也是百度...
感恩老师四年级作文600字【... 篇一:感恩老师的温暖我是一名四年级的学生,今天我要写一篇关于感恩老师的作文。老师是我们成长道路上最重...
做家务的作文400字四年级作... 做家务的作文400字四年级作文20篇篇一:我喜欢做家务我是一个四年级的小学生,我喜欢做家务。每天放学...
四年级写作文买菜400字(经... 四年级写作文买菜400字 篇一:去菜市场买菜四年级的我,有一次和妈妈一起去菜市场买菜,这是我第一次亲...
四年级下册数学暑假新时空答案... 四年级下册数学暑假新时空答案 篇一在四年级下册的数学学习中,我们将继续探索数学的奥秘和应用。而暑假正...
地雷花四年级作文【最新3篇】 地雷花四年级作文 篇一地雷花地雷花是一种奇特的植物,它的花朵看起来像一颗小小的地雷。我在学校的花坛里...
我和孙悟空的一天四年级上册作... 我和孙悟空的一天四年级上册作文 篇一今天是一个特殊的日子,我竟然和孙悟空一起度过了一天!早上,我醒来...
推荐一个好地方作文四年级45... 推荐一个好地方作文四年级450字 篇一我的家乡是一个非常美丽的地方,我推荐大家来参观一下。我家乡的风...
小学的小学四年级作文500字... 小学的小学四年级作文500字 篇一我喜欢的运动我是一个活泼好动的小女孩,喜欢参加各种运动。今天我就来...
今天我当家四年级作文(推荐6... 今天我当家四年级作文 篇一我当家的一天今天,我得到了一个特殊的任务,那就是要当家一天。这是我第一次担...
跳绳比赛四年级作文(通用6篇... 跳绳比赛四年级作文 篇一跳绳比赛的经历我是一名四年级的学生,最近我们学校举办了一场跳绳比赛。这是我第...
家乡变化作文400字四年级(... 家乡变化作文400字四年级 篇一家乡变化我爱我的家乡,那是一个美丽而又宁静的地方。然而,自从几年前,...
一件高兴的事250作文四年级... 篇一:一件高兴的事今天,我要给大家讲一个让我非常高兴的事情。那天,我和妈妈一起去超市买东西。在超市里...
四年级写大自然的景色作文30... 四年级写大自然的景色作文300字 篇一大自然的美景大自然是我们共同的母亲,给予我们无尽的美景和宝贵的...
小小动物园四年级作文【精简6... 小小动物园四年级作文 篇一我参观了一个小小的动物园,里面有许多有趣的动物。首先,我看到了一只可爱的小...