The core of watch is to observe a responsive data, notify and perform a callback when the data changes (that is, it is an effect itself).
The essence of Watch is that Effect collects the data that users fill in internally
When watch monitors an object, the change of the object’s property will not be triggered because the reference address of the object has not changed
Watch is equivalent to effect internally holding old and new values calling methods
Usage scenarios
1. The monitoring object can monitor data changes and re-execute the data changes
The monitored object cannot distinguish the new and old values
const state= reactive({flag: true.name: 'lyp'.address: {num: 6}, age: 30})
// Monitor the change of a response expression value
watch(state,(oldValue,newValue) = >{
console.log(oldValue,newValue)
})
setTimeout(() = > {
state.name ='jdlyp'
// Can also trigger watch
// state.address.num='10000'
}, 1000);
Copy the code
2, you can monitor a function, the return value of the function is the old value after the update of the new value
Num cannot be written as state.address.num
const state= reactive({flag: true.name: 'lyp'.address: {num: 6}, age: 30})
watch(() = >state.address.num,(oldValue,newValue) = >{ // Monitor the change of a response expression value
console.log(oldValue,newValue)
})
setTimeout(() = > {
state.address.num=10000
}, 1000);
Copy the code
3. When the watch is triggered continuously, the previous watch operation needs to be cleaned to achieve the purpose of taking the last return result as the criterion
When the user enters something in the input field, we return the result based on the input (ajax asynchronously)
Implementation steps
- 1) Pass in a cancelled callback the first time watch is called
- 2) Execute the callback passed in the previous time on the second call to watch
OnCleanup is a hook provided by vue source code to the user
Functions passed by the user to onCleanup are automatically called by the vue source code
const state= reactive({flag: true.name: 'lyp'.address: {num: 6}, age: 30})
let i = 2000;
// The simulated Ajax implementation returns later the first time than the second time
function getData(timer){
return new Promise((resolve,reject) = >{
setTimeout(() = >{ resolve(timer) }, timer); })}// The watch callback is executed every time the data changes
The onCleanup function, which forms a private scope each time, changes the clear value of the previous private scope
OnCleanup is a vue source code hook provided to the user
watch(() = >state.age,async (newValue,oldValue,onCleanup)=>{
let clear = false;
The call to the cleanup function, which is passed to the next layer in the vUE source code, is automatically executed by the vue source code
onCleanup(() = >{
clear = true;
})
i-=1000;
let r = await getData(i); // Render 1000 after 1s for the first time and 0 after 0s for the second time, which should be 0
if(! clear){document.body.innerHTML = r; }}, {flush:'sync'}); // {flush:'sync'} indicates synchronization
state.age = 31;
state.age = 32;
Copy the code
Code implementation
- 1. Loop over properties for responsive objects
- 2. If it is a function, let the function be fn
- 3, create effect to monitor the change of the function data, execute the job again, and obtain the new value
- 4, run to save the old value run to the getter (source) or loop source
- Need to execute immediately, execute the task immediately
- The onCleanup function is passed during the callback to expose the hook to the user
- 7, save the parameter passed by the user as cleanup
export const enum ReactiveFlags {
IS_REACTIVE = '__v_isReactive'
}
export function isReactive(value){
return!!!!! (value && value[ReactiveFlags.IS_REACTIVE]) }// If the object is traversed, consider the problem of circular references in the object
function traverse(value,seen = new Set(a)){
if(! isObject(value)){// No more recursion if it is not an object
return value
}
Object = {a:obj} {a:obj}
if(seen.has(value)){
return value;
}
seen.add(value);
for(const k in value){ // Recursive access properties are used for dependency collection
traverse(value[k],seen)
}
return value
}
// Source is the object passed in by the user. Cb is the user's callback immediate whether to execute a callback immediately
export function watch(source,cb,{immediate} = {} as any){
let getter;
// 1. Loop through the properties for a responsive object
if(isReactive(source)){
// Loop through the user's incoming data to collect effects only once
// For recursive loops, each property of the object is accessed as long as the loop is accessed. For effect properties, dependency collection is performed.
// The function is wrapped as fn corresponding to effect, and the function is traversed internally to achieve the purpose of dependent collection
getter = () = > traverse(source)
console.log(getter)
}else if(isFunction(source)){
getter = source // if the function is fn, let the function be fn
}
let oldValue;
let cleanup;
let onCleanup = (fn) = >{
cleanup = fn;// 7, save the parameter passed by the user as cleanup
}
const job = () = >{
// When the value changes, run the effect function again to obtain the new value
const newValue = effect.run();
// The first time the last registered callback is not called before the next watch execution
if(cleanup) cleanup();
// Call onCleanup to expose the hook to the user
cb(newValue,oldValue,onCleanup);
oldValue = newValue
}
// create effect to monitor the change of function data, execute job again and get the new value
const effect = new ReactiveEffect(getter,job)
if(immediate){ // 5. Execute tasks immediately if you need to execute them immediately
job();
}
// 4, run to save the old value of the getter (source) or loop source
oldValue = effect.run();
}
Copy the code
Step by step on
Monitor responsive objects
function traverse(value,seen = new Set(a)){
if(! isObject(value)){return value
}
if(seen.has(value)){
return value;
}
seen.add(value);
for(const k in value){ // Recursive access properties are used for dependency collection
traverse(value[k],seen)
}
return value
}
export function isReactive(value){
return!!!!! (value && value[ReactiveFlags.IS_REACTIVE]) }export function watch(source,cb){
let getter;
if(isReactive(source)){ // If it is a responsive object
getter = () = > traverse(source)// The function is wrapped as fn corresponding to effect, and the function is traversed internally to achieve the purpose of dependent collection
}
let oldValue;
const job = () = >{
const newValue = effect.run(); // When the value changes, run the effect function again to obtain the new value
cb(newValue,oldValue);
oldValue = newValue
}
const effect = new ReactiveEffect(getter,job) / / create the effect
oldValue = effect.run(); // Run to save the old value
}
Copy the code
Monitoring function
export function watch(source,cb){
let getter;
if(isReactive(source)){ // If it is a responsive object
getter = () = > traverse(source)
}else if(isFunction(source)){
getter = source // if it is a function, let the function be fn
}
// ...
}
Copy the code
Execution time of the callback in watch
export function watch(source,cb,{immediate} = {} as any){
const effect = new ReactiveEffect(getter,job) / / create the effect
if(immediate){ // The task needs to be executed immediately
job();
}
oldValue = effect.run();
}
Copy the code
Cleanup implementation in watch
When the watch is triggered continuously, previous watch operations need to be cleared
OnCleanup is a hook provided by vue source code to the user
Functions passed by the user to onCleanup are automatically called by the vue source code
/ / use
const state = reactive({ flag: true.name: 'lyp'.age: 30 })
let i = 2000;
function getData(timer){
return new Promise((resolve,reject) = >{
setTimeout(() = > {
resolve(timer)
}, timer);
})
}
watch(() = >state.age,async (newValue,oldValue,onCleanup)=>{
let clear = false;
onCleanup(() = >{ // Use the hook function to pass the cancelled callback to the next layer
clear = true;
})
i-=1000;
let r = await getData(i); // Render 1000 after 1s for the first time and 0 after 0s for the second time, which should be 0
if(! clear){document.body.innerHTML = r; }}, {flush:'sync'});
state.age = 31;
state.age = 32;
Copy the code
// Source code implementation
let cleanup;
let onCleanup = (fn) = >{
cleanup = fn; // Save the user's termination function
}
const job = () = >{
const newValue = effect.run();
if(cleanup) cleanup(); // The first time the last registered callback is not called before the next watch execution
cb(newValue,oldValue,onCleanup); // Call the user callback passed in the onCleanup function to expose the hook
oldValue = newValue
}
Copy the code