Computed 函数内部实现。 接收一个有返回值的函数作为参数。这个函数的返回值就是计算属性的值。并且我们要监听这个函数内容使用的响应式数据的变化,最后将这个函数执行的结果返回。
export functon computed(getter){
// 1. 最终要返回一个 ref创建的具有value属性的对象
const result = ref();
// 2. 监听响应式数据的变化。当数据变化后会重新执行effect函数,把getter的结果再存储到result中
effect(() => (result.value = getter()));
return result
}
复制代码
so,你肯定会觉得: 这么容易就没了?你在逗我玩,你个菜J。
1.从你用过的reactive开始
reactive
:
- 接收一个参数,判断这参数对象是否是Object
- 创建拦截器对象
handler
,设置set/get/deleteProperty
方法 return Proxy
export function reactive (target) {
// 先判断 target 是否是对象,不是对象之间 return 出去
if (!isObject(target)) return target
// 拦截器对象
const handler = {
get (target, key, receiver) {
// code...
},
set (target, key, value, receiver) {
// code...
},
deleteProperty (target, key) {
// code...
}
}
return new Proxy(target, handler)
}
复制代码
那我们的 get
方法目的是获取 target
的 key
的值。 如果 key
值本身还是个 Object
的话,那就得继续往下递归? 来,往下coding。
const convert = target => isObject(target) ? reactive(target) : target;
// 为了后续不再重复书写辅助类工具函数,先一并写上。直接看函数名你也就明白了。
const isObject = val => val !== null && typeof val === 'object'
const convert = target => isObject(target) ? reactive(target) : target
const hasOwnProperty = Object.prototype.hasOwnProperty
const hasOwn = (target, key) => hasOwnProperty.call(target, key);
let targetMap = new WeakMap();
// code ...
get (target, key, receiver) {
// 收集依赖
// track(target, key)
console.log('get:',key);
// 返回target洪的key的值
const result = Reflect.get(target, key, receiver);
return convert(result)
},
复制代码
所以,人人都说递归、闭包、嵌套循环不好。但你无法否认的是:真香……
继续。我们的 set
方法。
set (target, key, value, receiver) {
const oldValue = Reflect.get(target, key, receiver)
let result = true
if (oldValue !== value) {
result = Reflect.set(target, key, value, receiver)
// 触发更新
// trigger(target, key);
console.log('set key:',key,'value:',value);
}
return result
},
复制代码
是的,你没看错,你都 set
值了,当然要触发更新了。 但你以为现在就写 trigger
吗?
? 我们来看 deleteProperty
方法
deleteProperty (target, key) {
const hadKey = hasOwn(target, key); // 头上辣个辅助函数。 23333.
const result = Reflect.deleteProperty(target, key)
if (hadKey && result) {
// 触发更新
// trigger(target, key)
console.log('delete:',key);
}
return result
}
复制代码
trigger:
export function trigger (target, key) {
const depsMap = targetMap.get(target); // 憋问 targetMap 哪来的,最前面写了。
if (!depsMap) return
const dep = depsMap.get(key)
if (dep) {
dep.forEach(effect => {
effect()
})
}
}
复制代码
effect & track – 收集依赖
先来看个使用栗子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="module">
import { reactive, effect } from './text2.js'
const product = reactive({
name: '你大爷',
price: 100,
count: 1
})
let total = 0
effect(() => {
total = product.price * product.count
})
console.log(total) // 100
product.price = 2000
console.log(total) // 2000
product.count = 10
console.log(total) // 20000
</script>
</body>
</html>
复制代码
如果栗子中三个 console
的结果是正确的话,那 effect 究竟做了哪些事情?
- 首次加载,执行effect内部的箭头函数。 箭头函数中访问了 product 。 product 是 reactive返回的响应式对象,也就是代理对象。当我们访问product. price 属性的时候会触发price属性的get方法。 在get方法中要收集依赖。过程就是存储这个属性和这个回调函数。而属性又跟对象相关,所以在代理对象中的get方法中首先会存储target目标对象。然后是target对象的属性,以及箭头函数。
- 触发更新时,会根据属性找到对应的函数。
- 继续收集下一个属性的依赖。
let activeEffect = null
export function effect (callback) {
activeEffect = callback
callback() // 访问响应式对象属性,去收集依赖
activeEffect = null
}
复制代码
(默默吐槽下:和react是真的像,网上那些喷子说的也不是完全没道理。)
Track 方法
let targetMap = new WeakMap()
// 收集依赖
export function track (target, key) {
if (!activeEffect) return // 如果没有依赖,直接出去
let depsMap = targetMap.get(target) // 寻找目标对象的依赖
if (!depsMap) {
targetMap.set(target, (depsMap = new Map())); // 没找到就新创建一个depsMap
}
let dep = depsMap.get(key) // 寻找当前key 的 dep
if (!dep) {
depsMap.set(key, (dep = new Set())) // 没找到,添加new Set
}
dep.add(activeEffect)
}
// 然后去 get 方法里收集依赖
复制代码
3 trigger – 触发更新
export function trigger (target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const dep = depsMap.get(key)
if (dep) {
dep.forEach(effect => {
effect()
})
}
}
复制代码
看实操吧。。
4. ref
export function ref (raw) {
// 判断 raw 是否是ref 创建的对象,如果是的话直接返回
if (isObject(raw) && raw.__v_isRef) {
return
}
let value = convert(raw)
const r = {
__v_isRef: true, // 不要问为啥这个属性名非得长这样,Vue3源码里就是长这样
get value () {
track(r, 'value')
return value
},
set value (newValue) {
if (newValue !== value) {
raw = newValue
value = convert(raw)
trigger(r, 'value')
}
}
}
return r
}
复制代码
5. computed
export function computed (getter) {
const result = ref()
effect(() => (result.value = getter()))
return result
}
复制代码
请你回到文章开头, 此刻还会认为我是个菜J吗?是不是简单到哭?
End
最后来比较一下 reactive
和 ref
的区别
ref
可以把基本数据类型转成响应式对象ref
返回的对象,即使重新赋值新的对象,也依然是响应式的reactive
返回的对象,重新赋值会丢失响应式reactive
返回的对象无法解构。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END