【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
|
|—— snapshots
codegen.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 成 ast
const 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.source
let node = undefined
if (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.length
const 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];
// 移动光标
// <div
advanceBy(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>
// 先解析开始 tag
const 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 node
const 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 = {}) {
// 先生成 context
const context = createCodegenContext(ast, options);
const { push, mode } = context;
//1.先生成 preambleContext
if (mode === "module") {
genModulePreamble(ast, context);
} else {
genFunctionPreamble(ast, context);
}
const functionName = "render";
const args = ["_ctx"];
// _ctx,aaa,bbb,ccc
// 需要把 args 处理成 上面的 string
const 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.is
return 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 {
// core
reactive,
ref,
readonly,
// utilities
unref,
proxyRefs,
isRef,
toRef,
toRefs,
isProxy,
isReactive,
isReadonly,
isShallow,
// advanced
customRef,
triggerRef,
shallowRef,
shallowReactive,
shallowReadonly,
markRaw,
toRaw,
// effect
effect,
stop,
ReactiveEffect,
// effect scope
effectScope,
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 handler
const 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.provied
isMounted: 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 里面的 props
const { props, children } = instance.vnode;
initProps(instance, props);
// 2。处理 slots
initslots(instance, children);
// 源码里面有两种类型的 component
// 一种是基于 options 创建的
// 还有一种是 function 的
// 这里处理的是 options 创建的
// 叫做 stateful 类型
setupStatefulComponent(instance);
}
function setupStatefulComponent(instance) {
// todo
// 1,先创建代理 proxy
console.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 的时候传入 props
const { 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。处理 setupResult
handleSetupResult(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是如何升级的;