セクション4:vue3-WatchAPI実装の原則

ウォッチのコアは、応答データを監視し、データが変更されたときにコールバックを通知して実行することです(つまり、それ自体がエフェクトです)。

時計の本質は、効果が収集するためにユーザーによって入力されたデータに依存することです

監視監視オブジェクトが変更されても、オブジェクトの参照アドレスが変更されていないため、オブジェクトのプロパティはトリガーされません。

watchは、古い値と新しい値を保存してメソッドを呼び出すエフェクトと同等です。

使用するシーン

1.監視オブジェクトは、データの変更を監視し、データが変更されたときに再実行できます。

監視オブジェクトは、前後の新しい値と古い値を区別できません

const state= reactive({flag: true, name: 'lyp',address:{num: 6}, age: 30})
// 监测一个响应式值的变化
watch(state,(oldValue,newValue)=>{ 
    console.log(oldValue,newValue)
})
setTimeout(() => {
    state.name ='jdlyp'
    // 也可以触发watch
    // state.address.num='10000'
}, 1000);
复制代码

2.関数を監視できます。関数の戻り値は、古い値が更新された後に新しい値を取得することです。

state.address.numとして直接書き込むことはできません

const state= reactive({flag: true, name: 'lyp',address:{num: 6}, age: 30})
watch(()=>state.address.num,(oldValue,newValue)=>{ // 监测一个响应式值的变化
    console.log(oldValue,newValue)
})
setTimeout(() => {
    state.address.num=10000
}, 1000);
复制代码

3.ウォッチが継続的にトリガーされる場合、最後に返された結果の目的を達成するために、前のウォッチ操作をクリーンアップする必要があります。

ユーザーが入力ボックスに入力すると、入力内容に応じた結果が返されます(ajaxなどの非同期)

実装手順

  • 1)初めてwatchを呼び出すときに、キャンセルコールバックを渡します
  • 2)ウォッチが2回目に呼び出されると、渡された最後のコールバックが実行されます

onCleanupは、vueソースコードによってユーザーに提供されるフックです。

ユーザーからonCleanupに渡された関数は、vueソースコードによって自動的に呼び出されます

const state= reactive({flag: true, name: 'lyp',address:{num: 6}, age: 30})
let i = 2000;
// 模拟ajax  实现 第一次比第二次返回的晚
function getData(timer){ 
    return new Promise((resolve,reject)=>{
        setTimeout(() => {
            resolve(timer)
        }, timer);
    })
}

// 每次数据变化 都会执行watch的回调函数 
// 每次都会形成一个私有作用域 传入的onCleanup函数 执行改变的是上一个私有作用域的clear值
// onCleanup 是 vue源码提供给用户的钩子
watch(()=>state.age,async (newValue,oldValue,onCleanup)=>{
    let clear = false;
    
    // 将 终止的调函数 给到vue源码中的cleanup(也就是传递给下一层)  终止函数的调用会有vue源码自动执行
    onCleanup(()=>{  
        clear = true; 
    })
    i-=1000;
    let r =  await getData(i); // 第一次执行1s后渲染1000, 第二次执行0s后渲染0, 最终应该是0
    if(!clear){document.body.innerHTML = r;} 
},{flush:'sync'}); // {flush:'sync'} 表示同步执行
state.age = 31;
state.age = 32;
复制代码

コード

  • 1.レスポンシブオブジェクトの場合は、プロパティをループします
  • 2.関数の場合は、関数をfnとして使用します。
  • 3.自分で作成した関数データの変化を監視するエフェクトを作成し、ジョブを再実行して新しい値を取得します
  • 4.実行して古い値を保存する場合は、ゲッターを実行します。つまり、ソース実行またはループソースを実行します。
  • 5.すぐに実行する必要がある場合は、タスクをすぐに実行します
  • 6. onCleanup関数がコールバックで渡され、フックがユーザーに公開されます
  • 7.ユーザーから渡されたパラメーターをクリーンアップとして保存します
export const enum ReactiveFlags {
    IS_REACTIVE = '__v_isReactive' 
}
export function isReactive(value){
    return !!(value && value[ReactiveFlags.IS_REACTIVE])
}
// 遍历的是对象的话  考虑对象中有循环引用的问题
function traverse(value,seen = new Set()){ 
    if(!isObject(value)){ // 不是对象就不再递归了
        return value
    }
    // 如果循环过就不再考虑 直接返回上次的对象就行 解决 object= {a:obj} 的问题
    if(seen.has(value)){ 
        return value;
    }
    seen.add(value);
    for(const k in value){ // 递归访问属性用于依赖收集
        traverse(value[k],seen)
    }
    return value
}


// source 是用户传入的对象   cb就是对应的用户回调  immediate是否立即执行一次回调
export function watch(source,cb,{immediate} = {} as any){
    let getter;
    // 1、如果是响应式对象 循环一遍属性
    if(isReactive(source)){
        // 对用户传入的数据循环一遍来收集effect 只需要循环一遍就好
        //(递归循环,只要循环就会访问对象的每一个属性,在effect中 访问属性的时候 会进行依赖收集)
        // 包装成effect对应的fn, 函数内部进行遍历达到依赖收集的目的
        getter = () => traverse(source)
        console.log(getter)
    }else if(isFunction(source)){
        getter = source // 2、如果是函数则让函数作为fn即可
    }
    let oldValue;
    let cleanup;
    let onCleanup = (fn) =>{  
        cleanup = fn;// 7、保存用户传入的参数为cleanup
    }
    const job = () =>{
        // 值变化时再次运行effect函数,获取新值
        const newValue = effect.run(); 
        // 第一次没有 下次watch执行前调用上次注册的回调
        if(cleanup) cleanup(); 
        // 6、回调时传入onCleanup函数 将钩子暴露给用户
        cb(newValue,oldValue,onCleanup); 
        oldValue = newValue
    }
    // 3、创建effect 监控自己构造的函数 数据变化后重新执行job 然后获取新值
    const effect = new ReactiveEffect(getter,job) 
    if(immediate){ // 5、需要立即执行,则立刻执行任务
        job();
    }
    // 4、运行保存老值 run 的时候 让getter执行 也就是source执行 或者循环source
    oldValue = effect.run(); 
}
复制代码

ステップバイステップ

反応性オブジェクトの監視

function traverse(value,seen = new Set()){
    if(!isObject(value)){
        return value
    }
    if(seen.has(value)){
        return value;
    }
    seen.add(value);
    for(const k in value){ // 递归访问属性用于依赖收集
        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)){ // 如果是响应式对象
        getter = () => traverse(source)// 包装成effect对应的fn, 函数内部进行遍历达到依赖收集的目的
    }
    let oldValue;
    const job = () =>{
        const newValue = effect.run(); // 值变化时再次运行effect函数,获取新值
        cb(newValue,oldValue);
        oldValue = newValue
    }
    const effect = new ReactiveEffect(getter,job) // 创建effect
    oldValue = effect.run(); // 运行保存老值
}
复制代码

監視機能

export function watch(source,cb){
    let getter;
    if(isReactive(source)){ // 如果是响应式对象
        getter = () => traverse(source)
    }else if(isFunction(source)){
        getter = source // 如果是函数则让函数作为fn即可
    }
    // ...
}
复制代码

ウォッチでのコールバック実行タイミング

export function watch(source,cb,{immediate} = {} as any){
    const effect = new ReactiveEffect(getter,job) // 创建effect
    if(immediate){ // 需要立即执行,则立刻执行任务
        job();
    }
    oldValue = effect.run(); 
}
复制代码

ウォッチでのクリーンアップの実装

ウォッチが継続的にトリガーされる場合、前のウォッチ操作をクリーンアップする必要があります

onCleanupは、vueソースコードによってユーザーに提供されるフックです。

ユーザーからonCleanupに渡された関数は、vueソースコードによって自動的に呼び出されます

// 使用
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(()=>{ // 利用钩子函数将 取消的回调传给下一层
        clear = true;
    })
    i-=1000;
    let r =  await getData(i); // 第一次执行1s后渲染1000, 第二次执行0s后渲染0, 最终应该是0
    if(!clear){document.body.innerHTML = r;}
},{flush:'sync'});
state.age = 31;
state.age = 32;
复制代码
// 源码实现
let cleanup;
let onCleanup = (fn) =>{
    cleanup = fn; // 保存用户的终止函数
}
const job = () =>{
    const newValue = effect.run(); 
    if(cleanup) cleanup(); // 第一次没有 下次watch执行前调用上次注册的回调
    cb(newValue,oldValue,onCleanup); // 调用用户回调传入onCleanup函数 暴露钩子
    oldValue = newValue
}
复制代码

おすすめ

転載: juejin.im/post/7079322953174745125