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

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

这里还是想多说一点,源码几句话很难去解释清除,下面的核心代码都有写注释,大家按照思路一起走下去吧,本文末尾会有整体的流程图!

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 的类型部分环境判断断言等相关内容省略了

扫描二维码关注公众号,回复: 14598578 查看本文章

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

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


猜你喜欢

转载自blog.csdn.net/weixin_56650035/article/details/129232486