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