系列文章
【前端】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】