• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Vue3 核心模块源码

武飞扬头像
程邓楠
帮助37

Vue3 核心源码解析

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

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 核心

  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 成 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 方法主要做了一下几点事

  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 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-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 {
  // 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 —— 组件初始化

flowchart LR
	id1[开始] --> id2[1.创建 App]
	id1[开始] --> id3[2.进行初始化]
	id3[2.进行初始化] --> id4[1.基于 rootComponent 生成 vNode]
	id3[2.进行初始化] --> id5[2.进行 render]
	id5[2.进行 render] -->|调用 patch ,基于 vNode 类型 进行不同类型的组件处理| id6[处理 shapeFlag & ShapeFlag.COMPONENT 类型]
	id5[2.进行 render] -->|调用 patch ,基于 vNode 类型 进行不同类型的组件处理| id24[处理 shapeFlag & ShapeFlag.ELEMENT 类型]
	id6 --> id7[组件初始化]
	id6 --> id8[组件更新]
	id7 --> id9[1.创建 component instance 对象]
	id7 --> id10[2.setup component ]
	id10 --> id12[初始化 props]
	id10 --> id13[初始化 slot]
	id10 --> id14[初始化 setup ]
	id10 --> id15[初始化 render 函数]
	id7 --> id11[3.setupRenderEffect]
	id11 --> id16[1. 调用 render 函数获取 vnode -- 子组件]
	id11 --> id17[2. 触发生命周期 beforeMount Hook]
	id11 --> id18[3. 调用 patch 初始化子组件]
	id11 --> id19[4. 触发生命周期 mounted Hook]
	id8 --> id20[检测是否需要更新  对比props]
	id8 --> id21[提前更新组件 component 的数据,更新props,更新 slots]
	id8 --> id22[生成最新的 subTree]
	id8 --> id23[调用 patch 递归处理 subTree]
	
	id24 --> id25[element 初始化]
	id24 --> id26[element 更新]
        
	id25 --> id29[1. 调用 beforeCreateElement 创建真实 Element ]
	id25 --> id30[2. 处理 children 节点]
	id25 --> id31[3. 调用 hostPatchProp 设置元素的prop]
	id25 --> id32[4. 触发beforeMount 钩子]
	id25 --> id33[5. 渲染 hostInsert 插入真实的 dom 树]
	id25 --> id34[6. 触发 Mounted 钩子]

	id26 --> id27[对比 props]
	id26 --> id28[对比 children 递归遍历所有children 调用 patch]

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


这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanfekjc
系列文章
更多 icon
同类精品
更多 icon
继续加载