实现 computed 函数
特点
计算属性中的 get
和 set
用的是类中的属性访问器,而类中的属性访问器编译后的结果就是 defineProperty
默认不会执行,多次取值的时候,只要计算属性中依赖的值不变,那么就不会重新执行
传值的时候,如果传一个对象的话,就要传 get
和 set
,如果传一个函数的话,就是 get
第 1 步
packages/reactivity/src/computed.ts
import { isFunction } from "@vue/shared";
import { ReactiveEffect } from "./effect";
class ComputedRefImpl{
// 计算属性也有依赖收集
public dep
// 判断是否是一个脏的
public _dirty = true
public __v_isRef = true
// 计算属性本身就是一个 effect
public effect
public _value
constructor(getter, public setter) {
// 给当前的 effect 赋值,将计算属性包成一个 effect
this.effect = new ReactiveEffect(getter)
}
// 取值
get value() {
this._value = this.effect.run()
return this._value
}
// 设置值
set value(newValue) {
// 如果修改计算属性的值,那么则直接触发自己写的 set 函数
this.setter(newValue)
}
}
export function computed(getterOrOption) {
// 判断传过来的是否是 get + set 或者是只有一个 get
const onlyGetter = isFunction(getterOrOption)
// get 函数
let getter
// set 函数
let setter
if (onlyGetter) {
// 只有 get 函数
getter = getterOrOption
setter = () => {}
} else {
// 有 get 和 set
getter = getterOrOption.get
setter = getterOrOption.set
}
return new ComputedRefImpl(getter, setter)
}
packages/reactivity/src/index.ts
export { effect } from './effect'
export { reactive } from './reactive'
// --------------------------新增 start--------------------------
import { computed } from "./computed"
// --------------------------新增 end----------------------------
packages/shared/src/index.ts
export function isObject(value: unknown): value is Record<any, any> {
return typeof value === 'object' && value !== null
}
// --------------------------新增 start--------------------------
export function isFunction(value: unknown): boolean {
return typeof value === 'function'
}
// --------------------------新增 end----------------------------
packages/reactivity/example/computed.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script src="../dist/reactivity.global.js"></script>
<script>
const {reactive, effect, computed} = VueReactivity
const obj = {
name: 'xiaoming',
age: 12
}
const state = reactive(obj)
const age = computed({
get() {
console.log('get');
return state.age + 10
},
set(value) {
console.log(value);
}
})
effect(() => {
console.log(age.value);
console.log(age.value);
console.log(age.value);
})
setTimeout(() => {
state.age = 20
}, 2000)
</script>
</body>
</html>
效果如下:
第 2 步
问题描述:上面当多次调用计算属性的时候,会多次执行,并没有缓存packages/reactivity/src/computed.ts
import { isFunction } from "@vue/shared";
import { isTracking, ReactiveEffect } from "./effect";
class ComputedRefImpl{
// 计算属性也有依赖收集
public dep
// 判断是否是一个脏的
public _dirty = true
public __v_isRef = true
// 计算属性本身就是一个 effect
public effect
public _value
constructor(getter, public setter) {
// 给当前的 effect 赋值,将计算属性包成一个 effect
// 这里将计算属性变成了 effect,那么计算属性中的属性会收集这个 effect
this.effect = new ReactiveEffect(getter)
}
// 取值
get value() {
// --------------------------新增 start--------------------------
if (this._dirty) {
// 将结果缓存到 _value 中,这样就不用每次都调用 run 方法
this._value = this.effect.run()
this._dirty = false
}
return this._value
// --------------------------新增 end----------------------------
}
// 设置值
set value(newValue) {
// 如果修改计算属性的值,那么则直接触发自己写的 set 函数
this.setter(newValue)
}
}
export function computed(getterOrOption) {
// 判断传过来的是否是 get + set 或者是只有一个 get
const onlyGetter = isFunction(getterOrOption)
// get 函数
let getter
// set 函数
let setter
if (onlyGetter) {
// 只有 get 函数
getter = getterOrOption
setter = () => {}
} else {
// 有 get 和 set
getter = getterOrOption.get
setter = getterOrOption.set
}
return new ComputedRefImpl(getter, setter)
}
效果如下:
第 3 步
问题描述:将计算属性缓存了之后,当计算属性发生改变之后,拿到的依然是老值packages/reactivity/src/computed.ts
import { isFunction } from "@vue/shared";
import { isTracking, ReactiveEffect, trackEffects, triggerEffects } from "./effect";
class ComputedRefImpl{
// 计算属性也有依赖收集
public dep
// 判断是否是一个脏的
public _dirty = true
public __v_isRef = true
// 计算属性本身就是一个 effect
public effect
public _value
constructor(getter, public setter) {
// 给当前的 effect 赋值,将计算属性包成一个 effect
// 这里将计算属性变成了 effect,那么计算属性中的属性会收集这个 effect
// --------------------------新增 start--------------------------
this.effect = new ReactiveEffect(getter, () => {
// 当计算属性依赖的值变化的时,不要重新执行计算属性的 effect,而是调用此函数
if (!this._dirty) {
this._dirty = true
triggerEffects(this.dep)
}
})
// --------------------------新增 end----------------------------
}
// 取值
get value() {
// --------------------------新增 start--------------------------
// 判断是否在 effect 中取值的
if (isTracking()) {
trackEffects(this.dep || (this.dep = new Set))
}
// --------------------------新增 end----------------------------
if (this._dirty) {
// 将结果缓存到 _value 中,这样就不用每次都调用 run 方法
this._value = this.effect.run()
this._dirty = false
}
return this._value
}
// 设置值
set value(newValue) {
// 如果修改计算属性的值,那么则直接触发自己写的 set 函数
this.setter(newValue)
}
}
export function computed(getterOrOption) {
// 判断传过来的是否是 get + set 或者是只有一个 get
const onlyGetter = isFunction(getterOrOption)
// get 函数
let getter
// set 函数
let setter
if (onlyGetter) {
// 只有 get 函数
getter = getterOrOption
setter = () => {}
} else {
// 有 get 和 set
getter = getterOrOption.get
setter = getterOrOption.set
}
return new ComputedRefImpl(getter, setter)
}
packages/reactivity/src/effect.ts
...
// 需要记录 effect 依赖了哪些属性,同时让属性记录依赖了哪个effect
export class ReactiveEffect {
// 表示是否是响应式
active = true
// 表示 effect 依赖了哪些属性,同时当前属性依赖了哪个 effect
deps = []
// --------------------------新增 start--------------------------
constructor(public fn, public scheduler?) {
}
// --------------------------新增 end----------------------------
...
}
...
export function track(target, key) {
// 只有写在 effect 中的属性才需要收集,所以这里判断一下
if (!isTracking) {
return
}
// 判断 weakmap 中是否已经保存过该对象(target 在 weakmap 中是 key)
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
// 判断 weakmap 中是否已经保存过当前属性(当前属性在 weakmap 中是 value)
let dep = depsMap.get(key)
if (!dep) {
// 用 Set 类型是为了防止重复
dep = new Set()
depsMap.set(key, dep)
}
// --------------------------新增 start--------------------------
trackEffects(dep)
// --------------------------新增 end----------------------------
}
// --------------------------新增 start--------------------------
export function trackEffects(dep) {
// 判断当前 key 中是否已经有了当前的 effect
const shouldTrack = dep.has(activeEffect)
if (!shouldTrack) {
dep.add(activeEffect)
// 让 effect 来记录当前属性
activeEffect.deps.push(dep)
}
}
// --------------------------新增 end----------------------------
export function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) {
// 说明当前修改的对象并没有依赖任何 effect
return
}
const deps = []
if (key !== undefined) {
deps.push(depsMap.get(key))
}
let effects = []
for (const dep of deps) {
effects.push(...dep)
}
// --------------------------新增 start--------------------------
triggerEffects(effects)
// --------------------------新增 end----------------------------
}
// --------------------------新增 start--------------------------
export function triggerEffects(dep) {
for (const effect of dep) {
if (effect !== activeEffect) {
if (effect.scheduler) {
return effect.scheduler()
}
effect.run()
}
}
}
// --------------------------新增 end----------------------------
packages/reactivity/example/computed.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script src="../dist/reactivity.global.js"></script>
<script>
const {reactive, effect, computed} = VueReactivity
const obj = {
name: 'xiaoming',
age: 12
}
const state = reactive(obj)
const age = computed({
get() {
console.log('get');
return state.age + 10
},
set(value) {
console.log(value);
}
})
effect(() => {
console.log(age.value);
console.log(age.value);
console.log(age.value);
})
setTimeout(() => {
state.age = 20
}, 2000)
</script>
</body>
</html>
效果如下:
推荐文章: