当前位置: 首页 > news >正文

门户网站建设管理情况自查报告/百度运营优化师

门户网站建设管理情况自查报告,百度运营优化师,建站不备案,渭南几个区【Vue3 核心模块源码解析(上)】讲到了 Vue2 与 Vue3的一些区别,Vue3 新特性的使用,以及略微带了一点源码。那么这篇文章就要从Vue3 模块源码解析 与 Vue3 执行逻辑解析这两个方面去给大家剖析 Vue3 的深层次,一起学习起来吧! 这里…

【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])) {// 匹配 </div>// 需要改变 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];// 移动光标// <divadvanceBy(context, match[0].length);// 暂时不处理 selfclose 标签的情 ,所以可以直接 advanceBy 1个坐标< 的下一个就是 >advanceBy(context, 1);if (type === TagType.End) return;let tagType = ElementTypes.ELEMENT;return {type: NodeTypes.ELEMENT,tag,tagType,}
}function parseElement(context, ancestors) {// 应该如何解析 tag 呢// <div></div> // 先解析开始 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 是 <div><span></div>// 像这种情况下,其实就应该报错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.头部 是不是以 </ 开头的// 2.看看是不是和 tag 一样return (startswith(source, '</') && source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase())
}

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<object> = {get,set,deleteProperty,has,ownKeys
}export const readonlyHandlers: ProxyHandler<object> = {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.创建 App
2.进行初始化
1.基于 rootComponent 生成 vNode
2.进行 render
处理 shapeFlag & ShapeFlag.COMPONENT 类型
处理 shapeFlag & ShapeFlag.ELEMENT 类型
组件初始化
组件更新
1.创建 component instance 对象
2.setup component
初始化 props
初始化 slot
初始化 setup
初始化 render 函数
3.setupRenderEffect
1. 调用 render 函数获取 vnode -- 子组件
2. 触发生命周期 beforeMount Hook
3. 调用 patch 初始化子组件
4. 触发生命周期 mounted Hook
检测是否需要更新 对比props
提前更新组件 component 的数据,更新props,更新 slots
生成最新的 subTree
调用 patch 递归处理 subTree
element 初始化
element 更新
1. 调用 beforeCreateElement 创建真实 Element
2. 处理 children 节点
3. 调用 hostPatchProp 设置元素的prop
4. 触发beforeMount 钩子
5. 渲染 hostInsert 插入真实的 dom 树
6. 触发 Mounted 钩子
对比 props
对比 children 递归遍历所有children 调用 patch

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


相关文章:

Vue3 核心模块源码解析(中)

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

华为OD机试题 - 剩余可用字符集(JavaScript)| 含思路

华为OD机试题 最近更新的博客使用说明本篇题解:剩余可用字符集题目输入输出示例一输入输出说明Code解题思路华为OD其它语言版本最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全…...

焦虑的根源

归结起来&#xff0c;焦虑的原因就两条:想同时做很多事&#xff0c;又想立即看到效果。王小波说:人的一切痛苦&#xff0c;本质上都是对自己无能的愤怒。焦虑的本质也契合这一观点:自己的欲望大于能力&#xff0c;又极度缺乏耐心。焦虑就是因为欲望与能力之间差距过大。再往深了…...

1.认识网络爬虫

1.认识网络爬虫网络爬虫爬虫的合法性HTTP协议请求与响应(重点)网络爬虫 爬虫的全名叫网络爬虫&#xff0c;简称爬虫。他还有其他的名字&#xff0c;比如网络机器人&#xff0c;网络蜘蛛等等。爬虫就好像一个探测机器&#xff0c;它的基本操作就是模拟人的行为去各个网站溜达&am…...

【论文速递】WACV 2023 - 一种全卷积Transformer的医学影响分割模型

【论文速递】WACV 2023 - 一种全卷积Transformer的医学影响分割模型 【论文原文】&#xff1a;The Fully Convolutional Transformer for Medical Image Segmentation 【作者信息】&#xff1a;Athanasios Tragakis, Chaitanya Kaul,Roderick Murray-Smith,Dirk Husmeier 论…...

加密图像的脆弱水印及应用

原文题目&#xff1a;《A self-embedding secure fragile watermarking scheme with high quality recovery》 学习笔记&#xff1a; 应用场景 为了确保图像在传输过程中不被损坏&#xff0c;在将原始图像发送到云端之前&#xff0c;将用于篡改检测和恢复的水印嵌入到原始图像…...

python线上商城网站项目前台和后台源码

wx供重浩&#xff1a;创享日记 对话框发送&#xff1a;python51 获取完整源码源文件说明文档配置教程等 1、网站前台 在虚拟环境中启动程序后&#xff0c;使用浏览器访问“http://127.0.0.1:5000”即可进入网站前台首页。如图1所示。 单击首页左上角“注册”按钮&#xff0c;进…...

PowerShell 实现企业微信机器人推送消息

前言企业微信机器人 在ARMS告警管理中创建企业微信机器人后&#xff0c;您可以在通知策略中指定对应的企业微信群用于接收告警。当通知策略的匹配规则被触发时&#xff0c;系统会自动向您指定的企业微信群发送告警通知。企业微信群收到通知后&#xff0c;您可以在企业微信群中…...

IDEA集成Git就是这么简单

IDEA集成Git 文章目录IDEA集成Git配置Git环境配置Git的忽略文件①为什么需要配置忽略文件&#xff1f;②配置忽略文件③引用配置文件配置IDEA初始化项目添加到暂存区方式一&#xff1a;方式二&#xff1a;移除暂存区提交到本地库分支创建分支切换分支版本穿梭配置Git环境 配置…...

springBoot 事务基本原理

springBoot事务基本原理是基于spring的BeanPostProcessor&#xff0c;在springBoot中事务使用方式为&#xff1a; 一、在启动类上添加注解&#xff1a;EnableTransactionManagement 二、在需要事务的接口上添加注解&#xff1a;Transactional 基本原理&#xff1a; 注解&am…...

HBuilderX无线连接真机

说明 安装的是HBuilderX&#xff0c;不是HBuilder&#xff0c;adb.exe所在目录是 x:\HBuilderX\plugins\launcher\tools\adbs\ 里面可能有其他版本&#xff0c;用哪个都&#xff0c;建议使用最新的 配置 首先&#xff0c;将真机使用USB连接到电脑上。 在adb目录中启动命令…...

idea初学笔记

注:初学需安装idea专业版&#xff0c;方便学习使用idea运行内存配置从eclipse工具开发 转 idea工具开发&#xff0c;可设置idea快捷键同eclipse快捷键 file -> Settings -> Keymap -> 选择Eclipse -> OK设置idea项目整体编码格式file -> Settings -> Editor …...

C++核心编程<类和对象>(4)

C核心编程<类和对象>4.类和对象4.1封装4.1.1封装的意义封装的意义1封装的意义24.1.2struct和class区别4.1.3成员属性设置为私有4.2对象的初始化和清理4.2.1构造函数和析构函数1.1构造函数语法&#xff1a;类名(){}1.2析构函数语法&#xff1a; ~类名(){}4.2.2构造函数的分…...

编写http workshop脚本从网页缓存里解析音乐

前一篇文章 编写http workshop脚本从网站下载音乐 示范了如何使用HttpClient访问API,以及Json数据的解析; 今天我们通过解析一个网页展示如何使用内置的LibXml2的功能解析HTML,提取我们关心的内容。 这里随便搜了2个资源类的网站,竟然使用的格式是一模一样的: https://www…...

当数字孪生遇上轨道交通,会有什么新发展?

轨道交通是城市间互通互联的命脉&#xff0c;是当下人们出行的首要选择之一&#xff0c;也是我国“新基建”的重点建设对象。将城轨交通各链路系统及多类型服务&#xff0c;与空间感知、移动互联、云计算等技术深度融合&#xff0c;集中实现城市空间、城轨分布、城轨运行动态的…...

原理底层计划--分布式事务

分布式事务 mysql事务 我们通过show engines查询存储引擎&#xff0c;mysql一般为innodb, 为什么&#xff1f; 因为innodb支持事务是原因之一。 特性无非ACID 原子性&#xff0c;一致性&#xff0c;隔离性&#xff0c;持久性 一致性是最后追求的结果&#xff0c;也就保证了数…...

Hive总结

文章目录一、Hive基本概念二、Hive数据类型三、DDL,DML,DQL1 DDL操作2 DML操作3 DQL操作四、分区操作和分桶操作1、分区操作2、分桶操作五、Hive函数六、文件格式和压缩格式一、Hive基本概念 Hive是什么&#xff1f; Hive&#xff1a;由 Facebook 开源用于解决海量结构化日志的…...

docker环境下安装jenkins

前言 差点被Jenkins的插件搞麻了&#xff0c;又是依赖不对又是版本需要升级的&#xff0c;差点破口大骂了&#xff0c;还好忍住了&#xff0c;静下心来慢慢搞&#xff0c;终于搞通了。这里必须记录一下。 废话不多说&#xff0c;上来就是干&#xff0c;jenkins是干嘛用的&…...

Shifu基础功能:设备接入

如何修改设备接入的配置 1. 编辑edgedevice.yaml文件 接入设备前&#xff0c;您需要对edgedevice.yaml文件进行编辑。对于不同的协议&#xff0c;protocolSettings可根据协议进行进一步配置&#xff0c;详细配置请前往Shifu API参考。 ... connection: Ethernet address: …...

基于Java+SpringBoot+Vue+Redis+RabbitMq的鲜花商城

基于JavaSpringBootVueRedisRabbitMq的鲜花商城 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、…...

蓝桥杯真题(解码)小白入!

本来看这个题感觉很简单&#xff0c;不就是Ascall值换来换去嘛&#xff0c;其实也真的这样&#xff0c;但是对于小白来说&#xff0c;ascall根本记不住 题目说了&#xff0c;每个数不会重复超过9次&#xff08;这见到那多了&#xff0c;不然根本不会写&#xff09; 其次如何实现…...

并发包中的ConcurrentLinkedQueue和LinkedBlockingQueue有什么区别?

第20讲 | 并发包中的ConcurrentLinkedQueue和LinkedBlockingQueue有什么区别&#xff1f; 在上一讲中&#xff0c;我分析了 Java 并发包中的部分内容&#xff0c;今天我来介绍一下线程安全队列。Java 标准库提供了非常多的线程安全队列&#xff0c;很容易混淆。 今天我要问你的…...

分享四个前端Web3D动画库在Threejs中使用的动画库以及优缺点附地址

Threejs中可以使用以下几种动画库&#xff1a;Tween.js&#xff1a;Tween.js是一个简单的缓动库&#xff0c;可以用于在three.js中创建简单的动画效果。它可以控制数值、颜色、矢量等数据类型&#xff0c;并提供了多种缓动函数&#xff0c;例如线性、弹簧、强化、缓冲等等。区别…...

谷歌浏览器和火狐浏览器永久禁用缓存【一劳永逸的解决方式】

目录 前言 谷歌浏览器 方式一 方式二 火狐浏览器 前言 缓存对于开发人员来说异常的痛苦,很多莫名其妙的bug就是由缓存导致的,但当我们在网上查找禁用缓存的方式时,找到的方式大多数都是在开发者工具的面板中勾选禁用缓存的选项,但这种方式有个弊端就是需要一直打开这个…...

kibana查看日志

一、背景 kibana收集日志功能很强大&#xff0c;之前只是简单的使用&#xff0c;此次系统学习了解并分享一波 二、kibana查看日志的基本使用 1.选择查询的服务和日志文件 注意&#xff1a;每个应用配置了开发与生产环境&#xff0c;需要找到指定的应用 1.1选择对应的应用 1.…...

JS 异步接口调用介绍

JS 异步接口调用介绍 Js 单线程模型 JavaScript 语言的一大特点就是单线程&#xff0c;也就是说&#xff0c;同一个时间只能做一件事。这样设计的方案主要源于其语言特性&#xff0c;因为 JavaScript 是浏览器脚本语言&#xff0c;它可以操纵 DOM &#xff0c;可以渲染动画&a…...

5.深入理解HttpSecurity的设计

深入理解HttpSecurity的设计 一、HttpSecurity的应用 在前章节的介绍中我们讲解了基于配置文件的使用方式&#xff0c;也就是如下的使用。 也就是在配置文件中通过 security:http 等标签来定义了认证需要的相关信息&#xff0c;但是在SpringBoot项目中&#xff0c;我们慢慢脱离…...

opencv-python numpy常见的api接口汇总(持续更新)

前言 最近写代码总是提笔忘api&#xff0c;因为图像处理代码写的比较多&#xff0c;所以想着把一些常用的opencv的api&#xff0c;包括numpy的api做一个记录&#xff0c;后面再忘记的时候&#xff0c;就不用去google挨个搜索了&#xff0c;只需要在自己的博客中一查就全知道了…...

概率论小课堂:伯努利实验(正确理解随机性,理解现实概率和理想概率的偏差)

文章目录 引言I 伯努利试验1.1 伯努利分布(二项式分布)1.2 数学期望值(简称期望值)1.3 平方差(简称方差)1.4 标准差1.5 小结引言 假设买彩票中奖的概率是一百万分之一,如果要想确保成功一次,要买260万次彩票。你即使中一回大奖,花的钱要远比获得的多得多。 很多人喜…...

加密功能实现

文章目录1. 前言2. 密码加密1. 前言 本文 主要实现 对密码进行加密 &#xff0c;因为 使用 md5 容易被穷举 (彩虹表) 而破解 &#xff0c;使用 spring security 框架又太大了 (杀鸡用牛刀) 。   所以本文 就自己实现一个密码加密 . 2. 密码加密 这里我们通过 加盐是方式 来 对…...