【Vue3】源码解析-编绎模块

系列文章

【前端】Typescript入门
【Vue3】源码解析-前置
【Vue3】源码解析-响应式原理
【Vue3】源码解析-虚拟DOM
【Vue3】源码解析-编绎模块
【Vue3】源码解析-Runtime

computed.spec

computed 一般有两种常见的用法:
一:传入一个对象,内部有 set 和 get 方法,属于ComputedOptions形式。在内部会有getter / setter两个变量来进行保存.

const age = ref(18);
const myAge = computed({
  get() {},
  set() {},
});

二:传入一个 function,在内部会有getter来进行保存.

const age = ref(18);
const myAge = computed(() => {
  return age.value + 10;
});

计算属性的源码大部分是依赖 effect 的实现。基于上一篇文章对 effect 源码的理解,effect 可以传递一个函数和一个对象 options。
而计算属性的本质就是一个 effect,在之前 effect 的源码中预先声明了 lazy和scheduler 属性,就是用于计算属性,因为计算属性默认不会被执行,lazy 表示 effect 不会立即被执行,scheduler 会在 trigger 中判断是否传入了 scheduler,传入就执行 scheduler 方法。scheduler 中,判断当前的_dirty 是否为 false,会把_dirty 置为 true,且执行 trigger 触发响应。

// computed.ts
import { isFunction, TrackOpTypes, TriggerOrTypes } from "./shared";
import { effect, track, trigger } from "./effect";
 
class ComputedRefImpl {
  public _dirty = true; // 默认取值时不要用缓存
  public _value;
  public effect;
  constructor(getter, public setter) {
    this.effect = effect(getter, {
      // 1. 计算属性本身就是一个effect
      lazy: true, // 2. 默认不执行
      scheduler: () => {
        if (!this._dirty) {
          this._dirty = true;
          trigger(this, TriggerOrTypes.SET, "value");
        }
      },
    });
  }
  get value() {
    // 3.计算属性也要收集依赖,vue2中计算属性不具备收集依赖的,
    if (this._dirty) {
      this._value = this.effect(); // 4. 会将用户的返回值返回 也就是computed中 return
      this._dirty = false; // 5. 设置缓存
    }
    track(this, TrackOpTypes.GET, "value");  // 6. 依赖收集: 因为可能会在effect中使用计算属性
    /**
     *  const age = ref(18)
        const myAge = computed(() => { // 此方法默认不会被执行
            return age.value + 10;
        })
        // 当访问属性的时候执行
        console.log(myAge.value)
        console.log(myAge.value) // 多次取值,只取第一次执行结果走缓存
        age.value = 100; // 更新age,myAge不会立刻重新计算
        console.log(myAge.value) // 取值时才会重新计算
        effect(() => {  // 此effect中没有age 但是用到了计算属性,因此也需要依赖收集
            console.log(myAge.value)
        })
        age.value = 500  // 收集依赖后,属性值更新,需要在scheduler中触发trigger执行,
     */
    return this._value;   // 7. 多次取值,只取第一次执行结果走缓存
  }
  set value(newValue) {
    this.setter(newValue);
  }
}
 
// 1. 如果getterOrOptions是函数,会直接赋值给getter。并在用户进行复制操作时 给出只读提示
// 2. 否则getterOrOptions为对象,会将set和get分别赋值给setter,getter。
export function computed(getterOrOptions) {
  let getter;
  let setter;
  // 1. 如果getterOrOptions是函数,会直接赋值给getter。并在用户进行复制操作时 给出只读提示
  if (isFunction(getterOrOptions)) {
    getter = getterOrOptions;
    setter = () => {
      console.warn("computed value must be readonly");
    };
  } else {
    // 2. 否则getterOrOptions为对象,会将set和get分别赋值给setter,getter。
    getter = getterOrOptions.get;
    setter = getterOrOptions.set;
  }
  return new ComputedRefImpl(getter, setter);  // 3. 创建一个计算属性实例
}

parse.spec

根据参数options 和 html字符串执行 createParserContext 生成解析对象 context ,属性 options 值 为 参数 options 和默认配置 defaultParserOptions, 属性 source 和 originalSource 为参数 html字符串 ,barseParse 执行完后返回的是 ast 语法树

export function baseParse(
  content: string,
  options: ParserOptions = {}
): RootNode {
  const context = createParserContext(content, options)
  const start = getCursor(context)
  return createRoot(
    parseChildren(context, TextModes.DATA, []),
    getSelection(context, start)
  )
}

function createParserContext(
  content: string,
  rawOptions: ParserOptions
): ParserContext {
  const options = extend({}, defaultParserOptions)

  let key: keyof ParserOptions
  for (key in rawOptions) {
    // @ts-ignore
    options[key] =
      rawOptions[key] === undefined
        ? defaultParserOptions[key]
        : rawOptions[key]
  }
  return {
    options,
    column: 1,
    line: 1,
    offset: 0,
    originalSource: content,
    source: content,
    inPre: false,
    inVPre: false,
    onWarn: options.onWarn
  }
}

compile.spec

编译器的总入口,是编译器的一个基础骨架(概念上可以理解为基类),然后不同平台的编译系统都是基于baseCompile来进行扩展的,如dom编译、服务端渲染编译、sfc,都是基于baseCompile进行了对应平台下处理场景的扩展。

function baseCompile(
  template: string | RootNode,
  options: CompilerOptions = {}
): CodegenResult {
  // 省略无关代码...

  // 生成AST节点树
  const ast = isString(template) ? baseParse(template, options) : template
  // 获取节点转换工具集、指令转换工具集
  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset(
    prefixIdentifiers
  )
  // 遍历AST节点树,对上面生成的AST进行指令转换,生成可用节点,同时根据compiler
  // 传入的配置(如是否做静态节点提升等)对AST节点树进行优化处理,为rootNode及
  // 下属每个节点挂载codegenNode
  transform(
    ast,
    extend({}, options, {
      prefixIdentifiers,
      nodeTransforms: [
        ...nodeTransforms,
        ...(options.nodeTransforms || []) // user transforms
      ],
      directiveTransforms: extend(
        {},
        directiveTransforms,
        options.directiveTransforms || {} // user transforms
      )
    })
  )
  
  // 对转换及优化后的AST进行代码生成
  return generate(
    ast,
    extend({}, options, {
      prefixIdentifiers
    })
  )
}

来源

编绎模块
Vue3 之 computed 计算属性的使用与源码分析详细注释
Vue3.0源码解读 - 编译系统
vue3源码阅读【compiler-core\src\parse.ts】【baseParse】

  • 15
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

软泡芙

给爷鞠躬!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值