门户网站建设管理情况自查报告/百度运营优化师
【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
核心
- 把用户输入的内容做了
AST
的转换,- 转译成
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
方法主要做了一下几点事
- 创建 context
- 递归遍历 node, 针对不同的类型(NodeTypes)做不同的处理
- 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-dom
、runtime-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 —— 组件初始化
结语:到此 【Vue3 核心模块源码解析(中)】结束,本篇主要是以 繁琐的代码块为主,配合上 init 整体的流程图,分享了精简后大致的源码;
当然,最关键的 Diff 还没有讲到,Vue2、Vue3、React 的DIff 有什么区别,Vue3 中的 Diff是如何升级的;
相关文章:

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

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

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

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

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

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

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

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

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

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

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

idea初学笔记
注:初学需安装idea专业版,方便学习使用idea运行内存配置从eclipse工具开发 转 idea工具开发,可设置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构造函数语法:类名(){}1.2析构函数语法: ~类名(){}4.2.2构造函数的分…...

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

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

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

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

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

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

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

蓝桥杯真题(解码)小白入!
本来看这个题感觉很简单,不就是Ascall值换来换去嘛,其实也真的这样,但是对于小白来说,ascall根本记不住 题目说了,每个数不会重复超过9次(这见到那多了,不然根本不会写) 其次如何实现…...

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

分享四个前端Web3D动画库在Threejs中使用的动画库以及优缺点附地址
Threejs中可以使用以下几种动画库:Tween.js:Tween.js是一个简单的缓动库,可以用于在three.js中创建简单的动画效果。它可以控制数值、颜色、矢量等数据类型,并提供了多种缓动函数,例如线性、弹簧、强化、缓冲等等。区别…...

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

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

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

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

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

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

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