Computed functions are implemented internally. Take a function that returns a value as an argument. The return value of this function is the value of the evaluated property. We will listen for changes in the reactive data used by the function contents and return the result of the function execution.
export functon computed(getter){
// 1. Finally return an object created by ref with the value attribute
const result = ref();
// 2. Listen for changes in responsive data. When the data changes, the effect function is executed again, and the result of the getter is stored in the result
effect(() = >(the result value = getter ()));return result
}
Copy the code
So, you must think: so easy? You got to be kidding me, you asshole.
Start with reactive
Reactive:
- Receive a parameter and determine if the parameter Object is Object
- Create an interceptor object
handler
To set upset/get/deleteProperty
methods return Proxy
export function reactive (target) {
// Check whether target is an object, not between objects
if(! isObject(target))return target
// The interceptor object
const handler = {
get (target, key, receiver) {
// code...
},
set (target, key, value, receiver) {
// code...
},
deleteProperty (target, key) {
// code...}}return new Proxy(target, handler)
}
Copy the code
So the purpose of our get method is to get the key value of the target. If the key itself is still an Object, then you have to keep recursing, right? Let’s go to coding.
const convert = target= > isObject(target) ? reactive(target) : target;
// In order not to write the helper functions again, write them together first. Just look at the name of the function and you get the idea.
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(a);// code ...
get (target, key, receiver) {
// Collect dependencies
// track(target, key)
console.log('get:',key);
// Return the key value of the target flood
const result = Reflect.get(target, key, receiver);
return convert(result)
},
Copy the code
So, everyone says recursion, closures, nested loops are bad. But you can’t deny it: it smells good…
To continue. Our set method.
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 the update
// trigger(target, key);
console.log('set key:',key,'value:',value);
}
return result
},
Copy the code
Yes, you read that right, you set the value, so of course it triggers the update. But do you think you’re writing trigger now?
👌 Let’s look at the deleteProperty method
deleteProperty (target, key) { const hadKey = hasOwn(target, key); // Make your head hot. Const result = reflect.deleteProperty (target, key) if (hadKey && result) {// trigger(target, key) console.log('delete:',key); } return result }Copy the code
The trigger:
export function trigger (target, key) {
const depsMap = targetMap.get(target); // "targetMap" = "targetMap";
if(! depsMap)return
const dep = depsMap.get(key)
if (dep) {
dep.forEach(effect= > {
effect()
})
}
}
Copy the code
Effect & Track – Collect dependencies
Let’s start with using chestnuts
<! DOCTYPEhtml>
<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: 'Your uncle'.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>
Copy the code
If the result of three consoles in chestnuts is correct, what exactly did Effect do?
- First load, execute the arrow function inside effect. The arrow function accesses product. Product is the reactive object returned by reactive. When we access the product. price property we trigger the get method on the price property. Dependencies are collected in the GET method. The procedure is to store the property and the callback function. Properties are related to objects, so the target object is first stored in the get method of the proxy object. Then there are the properties of the target object, and the arrow function.
- When an update is triggered, the corresponding function is found based on the property.
- Continue collecting dependencies for the next property.
let activeEffect = null
export function effect (callback) {
activeEffect = callback
callback() // Access the reactive object properties to collect dependencies
activeEffect = null
}
Copy the code
(Just kidding: Like React, what the trolls say is not totally unreasonable.)
Track method
let targetMap = new WeakMap(a)// Collect dependencies
export function track (target, key) {
if(! activeEffect)return // If there is no dependency, go straight out
let depsMap = targetMap.get(target) // Find the dependencies of the target object
if(! depsMap) { targetMap.set(target, (depsMap =new Map()));// 没找到就新创建一个depsMap
}
let dep = depsMap.get(key) // Find the dep of the current key
if(! dep) { depsMap.set(key, (dep =new Set())) // Not found, add new Set
}
dep.add(activeEffect)
}
// Then go to the get method to collect dependencies
Copy the code
3 trigger – Triggers the update
export function trigger (target, key) {
const depsMap = targetMap.get(target)
if(! depsMap)return
const dep = depsMap.get(key)
if (dep) {
dep.forEach(effect= > {
effect()
})
}
}
Copy the code
Let’s see.
4. ref
export function ref (raw) {
// Check whether raw is an object created by ref, and return if it is
if (isObject(raw) && raw.__v_isRef) {
return
}
let value = convert(raw)
const r = {
__v_isRef: true.// Don't ask why the property name has to look like this, it looks like this in the Vue3 source code
get value () {
track(r, 'value')
return value
},
set value (newValue) {
if(newValue ! == value) { raw = newValue value = convert(raw) trigger(r,'value')}}}return r
}
Copy the code
5. computed
export function computed (getter) {
const result = ref()
effect(() = > (result.value = getter()))
return result
}
Copy the code
Please go back to the beginning of the article. Do you still think I am a dish J? Is it simple enough to cry?
End
Finally, let’s comparereactive
和 ref
The difference between
ref
You can turn basic data types into responsive objectsref
The returned object is responsive even if it is assigned a new objectreactive
Object that is returned. Reassigning will lose the responsereactive
The returned object cannot be deconstructed.