欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue.js實現(xiàn)watch屬性的示例詳解

 更新時間:2022年04月12日 09:40:29   作者:一川  
本文討論了watch函數(shù)是如何利用副作用函數(shù)和options進行封裝實現(xiàn)的,也通過調度函數(shù)去控制回調函數(shù)的立即執(zhí)行和執(zhí)行時機,還可以解決競態(tài)問題,感興趣的可以了解一下

1.寫在前面

在上篇文章中,我們討論了compted的實現(xiàn)原理,就是利用effect和options參數(shù)進行封裝。同樣的,watch也是基于此進行封裝的,當然watch還可以傳遞第三參數(shù)去清理過期的副作用函數(shù)。不僅可以利用副作用函數(shù)的調度性,去實現(xiàn)回調函數(shù)的立即執(zhí)行,也可以控制回調函數(shù)的執(zhí)行時機。

2.watch的實現(xiàn)原理

watch本質就是去觀測一個響應式數(shù)據(jù),當數(shù)據(jù)變化時通知并執(zhí)行相應的回調函數(shù)。watch的實現(xiàn)本質和computed類似,基于effect函數(shù)和options.scheduler選項。

const data = {
  name:"pingping",
  age:18,
  flag:true
};

const state = new Proxy(data,{
  /*...*/
})

watch(state,()=>{
  console.log("數(shù)據(jù)變化了呀...");
});

//在響應數(shù)據(jù)的age值被修改了,就會導致回調函數(shù)執(zhí)行
state.age++;

watch函數(shù)的實現(xiàn)代碼如下所示,副作用函數(shù)存在scheduler選項中,當響應數(shù)據(jù)發(fā)生變化時,會觸發(fā)scheduler調度函數(shù)執(zhí)行,而非直接觸發(fā)副作用函數(shù)執(zhí)行。

//watch 函數(shù)接收source響應數(shù)據(jù)和回調函數(shù)cb
function watch(source, cb){
  effect(
    //觸發(fā)讀取操作,建立聯(lián)系
    ()=>source.age,
    {
      scheduler(){
        // 當數(shù)據(jù)發(fā)生變化,調用回調函數(shù)cb
        cb();
      }
    }
  )
}

上面代碼片段中,我們在使用watch對數(shù)據(jù)進行監(jiān)聽時,只能對soure.age的變化進行觀測,不具有通用性,對此需要進行進一步封裝。

function watch(source, cb){
  effect(
    //調用traverse函數(shù)遞歸讀取操作,建立聯(lián)系
    ()=>traverse(source),
    {
      scheduler(){
        // 當數(shù)據(jù)發(fā)生變化,調用回調函數(shù)cb
        cb();
      }
    }
  )
}
const isObject = (value:any) => typeof value === "object" && value !== null;

function traverse(value, seen = new Set()){
  //如果讀取的數(shù)據(jù)是原始值,或已經讀取過響應數(shù)據(jù),則什么也不做
  if(!isObject(value) || seen.has(value)) return;
  //將數(shù)據(jù)添加到seen中,表示遍歷讀取過數(shù)據(jù),避免循環(huán)引用導致死循環(huán)
  seen.add(value);
  //對數(shù)據(jù)對象進行遍歷遞歸讀取,用于依賴收集
  for(const k in value){
    traverse(value[k],seen);
  }
  return value;
}

在上面代碼中,單獨封裝了一個遞歸函數(shù)traverse可以對響應數(shù)據(jù)進行遍歷遞歸讀取操作,這樣就可以讀取到對象的上所有屬性,從而監(jiān)聽任意屬性值發(fā)生變化時都能夠觸發(fā)回調函數(shù)。

事實上,使用watch進行數(shù)據(jù)觀測時,不僅可以觀測響應數(shù)據(jù),還可以觀測getter函數(shù)。那么,我們只需要先對輸入的被觀測數(shù)據(jù)判斷數(shù)據(jù)類型是否為function,如果是則賦值給getter,否則還是監(jiān)聽響應式數(shù)據(jù)。

function watch(source,cb){
    let getter;
    if(typeof source === "function"){ // 如果是函數(shù)表示是getter,可以直接賦值
       getter = source;
    }else{
       getter = () => traverse(source)// 包裝成effect對應的effectFn, 函數(shù)內部進行遍歷達到依賴收集的目的
    }
    let oldValue, newValue;
    const effectFn = effect(
      ()=>getter(),
      {
        //開啟lazy選項,將返回值存儲到effectFn中以便于之后手動調用
        lazy: true,
        scheduler(){
          newValue = effectFn(); // 值變化時再次運行effect函數(shù),獲取新值
          cb(newValue,oldValue);
          //更新舊值,不然下次得到的是錯誤的舊值
          oldValue = newValue;
        }
      }
    )
    //手動調用副作用函數(shù),拿到的值是舊值
    oldValue = effectFn();
}

其實,上面代碼中充分了利用了lazy選項的特性,利用其創(chuàng)建一個懶執(zhí)行的effect。通過手動執(zhí)行effectFn函數(shù)得到的返回值是舊值,當數(shù)據(jù)變化并觸發(fā)scheduler調度器執(zhí)行時,會重新執(zhí)行effectFn函數(shù)并且得到新值。

這樣,我們獲取到了數(shù)據(jù)變化前后的新值和舊值,可以將其作為參數(shù)傳遞給回調函數(shù)cb,在變化執(zhí)行副作用函數(shù)后需要將新值賦值給oldValue,方便后續(xù)執(zhí)行計算,否則后續(xù)變更會獲取到錯誤的舊值。

寫個demo使用下:

watch(
  ()=>state.age,
  (newValue, oldValue)=>{
      console.log(newValue, oldValue);
  }
)

state.age++

3.立即執(zhí)行的watch與回調執(zhí)行時機

watch本質上對effect的二次封裝,其具有兩個特性:立即執(zhí)行的回調函數(shù)、回調函數(shù)的執(zhí)行時機。

立即執(zhí)行的回調函數(shù)

立即執(zhí)行的回調函數(shù),默認情況下,一個watch的回調函數(shù)只會在響應數(shù)據(jù)發(fā)生變化時才執(zhí)行,但是在Vue.js中可以通過options.immediate來指定回調是否立即執(zhí)行。

當options.immediate存在且為true時,回調函數(shù)在該watch創(chuàng)建時立即執(zhí)行一次。事實上,回調函數(shù)的立即執(zhí)行和后續(xù)執(zhí)行在本質上區(qū)別不大,對此可以將其調度器scheduler進行封裝為通用函數(shù),通過options.immediate的存在與否判斷是在初始化還是變更時進行執(zhí)行。

function watch(source, cb, options={}){
  let getter;
  if(type === "function"){
    getter = source;
  }else{
    getter = ()=>traverse(source);
  }
  
  let oldValue, newValue;
  
  // 提取調度函數(shù)為獨立的函數(shù)
  const scheduler = ()=>{
    newValue = effectFn(); // 值變化時再次運行effect函數(shù),獲取新值
    cb(newValue,oldValue);
    //更新舊值,不然下次得到的是錯誤的舊值
    oldValue = newValue;
  }
  
  const effectFn = effect(
    ()=>getter(),
    {
      //開啟lazy選項,將返回值存儲到effectFn中以便于之后手動調用
      lazy: true,
      scheduler: scheduler
    }
  )
  if(options.immediate){
    //當immediate為true時,立即執(zhí)行scheduler函數(shù)從而觸發(fā)回調執(zhí)行
    scheduler()
  }else{
    //手動調用副作用函數(shù),拿到的值是舊值
    oldValue = effectFn();
  }
}

上面代碼中,回調函數(shù)是立即執(zhí)行的,在第一次回調函數(shù)執(zhí)行時沒有所謂的舊值,此時回調函數(shù)的oldValue值是undefined。

回調函數(shù)的執(zhí)行時機

當然,除了上面的可以指定回調函數(shù)為立即執(zhí)行外,還可以通過options參數(shù)來指定回調函數(shù)的執(zhí)行時機。在Vue.js3中可以通過flush選項來指定調度函數(shù)的執(zhí)行時機,當flush的值為"post"時,表示調度函數(shù)需要將副作用函數(shù)放在微任務隊列中,等待DOM更新結束后執(zhí)行。

function watch(source, cb, options={}){
  let getter;
  if(type === "function"){
    getter = source;
  }else{
    getter = ()=>traverse(source);
  }
  
  let oldValue, newValue;
  
  // 提取調度函數(shù)為獨立的函數(shù)
  const obj = ()=>{
    newValue = effectFn(); // 值變化時再次運行effect函數(shù),獲取新值
    cb(newValue,oldValue);
    //更新舊值,不然下次得到的是錯誤的舊值
    oldValue = newValue;
  }
  
  const effectFn = effect(
    ()=>getter(),
    {
      //開啟lazy選項,將返回值存儲到effectFn中以便于之后手動調用
      lazy: true,
      scheduler(){
        if(options.flush === "post"){
          const p = Promise.resolve();
          p.then(obj);
        }else{
          obj();
        }
      }
    }
  )
  if(options.immediate){
    //當immediate為true時,立即執(zhí)行scheduler函數(shù)從而觸發(fā)回調執(zhí)行
    scheduler()
  }else{
    //手動調用副作用函數(shù),拿到的值是舊值
    oldValue = effectFn();
  }
}

其實就是根據(jù)options.flush是否等于"post",來實現(xiàn)是否需要將obj函數(shù)進行異步處理。

4.過期的副作用函數(shù)和cleanup

在講到watch過期的副作用函數(shù),就要提到在多進程或多線程編程中經常被提及的競態(tài)問題。在下面代碼片段中,使用watch觀測state對象的變化,每次state對象發(fā)生變化時都會發(fā)送網(wǎng)絡請求。

let finalData;

watch(state, async ()=>{
  //發(fā)送等待網(wǎng)絡請求
  const res = await fetch("/user/info");
  finalData = res;
})

在上面代碼看起來是沒啥問題,但其實會發(fā)生競態(tài)問題,在第一次修改state對象的字段值后,會導致回調執(zhí)行,同時發(fā)送第一次請求A;在A請求返回結果之前,我們繼續(xù)修改state的字段值,同時發(fā)送第二次請求B。但是請求A和請求B誰的結果先返回,我們并不知道?

將A的請求結果覆蓋B的請求結果

在理論分析下,我們先后發(fā)送A、B請求,按道理應該是先返回A,再返回B請求的結果。這是因為請求A是副作用函數(shù)第一次執(zhí)行產生的副作用,而請求B是副作用函數(shù)第二次執(zhí)行產生的副作用。請求B在請求A后發(fā)生,請求A應當在這之前就過期了,返回的結果應該是無效的。

但是,在前面沒有對watch的執(zhí)行時機進行調度的情況下,就會發(fā)生請求A的值后返回覆蓋B請求返回值的錯誤。

要想解決這種問題,我們只需要提供一個副作用過期的手段即可。事實上,watch函數(shù)的回調函數(shù)可以傳入第三個參數(shù)onInvalidate函數(shù),讓其注冊一個回調在當前副作用函數(shù)過期時執(zhí)行:

function watch(source, cb, options={}){
  let getter;
  if(type === "function"){
    getter = source;
  }else{
    getter = ()=>traverse(source);
  }
  
  let oldValue, newValue;
  
  let cleanupFn;//用于存儲用戶注冊的過期回調
  //定義onInvalidate函數(shù)
  const onInvalidate = (fn)=>{
    //將過期的回調函數(shù)存儲到cleanupFn中
    fn();
  }
  
  // 提取調度函數(shù)為獨立的函數(shù)
  const obj = ()=>{
    newValue = effectFn(); // 值變化時再次運行effect函數(shù),獲取新值
    
    //在調用回調函數(shù)cb之前,先調用過期的回調函數(shù)
    if(cleanupFn){
      cleanupFn();
    }
    cb(newValue,oldValue,onInvalidate);
    //更新舊值,不然下次得到的是錯誤的舊值
    oldValue = newValue;
  }
  
  const effectFn = effect(
    ()=>getter(),
    {
      //開啟lazy選項,將返回值存儲到effectFn中以便于之后手動調用
      lazy: true,
      scheduler(){
        if(options.flush === "post"){
          const p = Promise.resolve();
          p.then(obj);
        }else{
          obj();
        }
      }
    }
  )
  if(options.immediate){
    //當immediate為true時,立即執(zhí)行scheduler函數(shù)從而觸發(fā)回調執(zhí)行
    scheduler()
  }else{
    //手動調用副作用函數(shù),拿到的值是舊值
    oldValue = effectFn();
  }
}

在上面代碼片段中,定義變量存儲用戶通過onInvalidate函數(shù)注冊的回調函數(shù),將過期的回調賦值給cleanupFn,在job函數(shù)中每次執(zhí)行回調函數(shù)cb前都會檢查是否存在過期回調。存在過期回調則執(zhí)行cleanupFn函數(shù)清理,最后將onInvalidate返回給用戶使用。

寫個demo實踐下:

watch(state, async (newValue, oldValue, onInvalidate)=>{
  let expired = false;
  onInvalidate(()=>{
    expired = true;
  })
  
  const res = await fetch("/user/info");
  
  if(!expired){
    finaleData = res;
  }
});

//第一次修改
state.age++;
setTimeout(()=>{
  state.age++;
},200)

示意圖如下:

請求過期

5.寫在最后

本文中,討論了watch函數(shù)是如何利用副作用函數(shù)和options進行封裝實現(xiàn)的,也通過調度函數(shù)去控制回調函數(shù)的立即執(zhí)行和執(zhí)行時機,還可以解決競態(tài)問題。

以上就是Vue.js實現(xiàn)watch屬性的示例詳解的詳細內容,更多關于Vue.js watch屬性的資料請關注腳本之家其它相關文章!

相關文章

  • 詳解Vue 全局變量,局部變量

    詳解Vue 全局變量,局部變量

    這篇文章主要介紹了Vue全局變量局部變量,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-04-04
  • vue + el-form 實現(xiàn)的多層循環(huán)表單驗證

    vue + el-form 實現(xiàn)的多層循環(huán)表單驗證

    這篇文章主要介紹了vue + el-form 實現(xiàn)的多層循環(huán)表單驗證,幫助大家更好的理解和使用vue框架,感興趣的朋友可以了解下。
    2020-11-11
  • Vue繪制雙Y軸折線柱狀圖

    Vue繪制雙Y軸折線柱狀圖

    這篇文章主要介紹了Vue繪制雙Y軸折線柱狀圖實例,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • vite+vue3項目集成ESLint與prettier的過程詳解

    vite+vue3項目集成ESLint與prettier的過程詳解

    這篇文章主要介紹了vite+vue3項目中集成ESLint與prettier的相關知識,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-09-09
  • vue router返回到指定的路由的場景分析

    vue router返回到指定的路由的場景分析

    這篇文章主要介紹了vue router返回到指定的路由的場景分析,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-11-11
  • Element?Plus在el-form-item中設置justify-content無效的解決方案

    Element?Plus在el-form-item中設置justify-content無效的解決方案

    這篇文章主要介紹了Element?Plus在el-form-item中設置justify-content無效的解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • vue中$set用法詳解

    vue中$set用法詳解

    在vue中,并不是任何時候數(shù)據(jù)都是雙向綁定的,解決數(shù)據(jù)沒有被雙向綁定我們可以使用?vm.$set?實例方法,該方法是全局方法?Vue.set?的一個別名,這篇文章主要介紹了vue中$set用法詳細講解,需要的朋友可以參考下
    2022-12-12
  • vue子元素綁定的事件, 阻止觸發(fā)父級上的事件處理方式

    vue子元素綁定的事件, 阻止觸發(fā)父級上的事件處理方式

    這篇文章主要介紹了vue子元素綁定的事件, 阻止觸發(fā)父級上的事件處理方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • vue中img或元素背景圖片無法顯示或路徑錯誤的解決

    vue中img或元素背景圖片無法顯示或路徑錯誤的解決

    這篇文章主要介紹了vue中img或元素背景圖片無法顯示或路徑錯誤的解決方案,具有很好的參考價值。希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • Vue進階之利用transition標簽實現(xiàn)頁面跳轉動畫

    Vue進階之利用transition標簽實現(xiàn)頁面跳轉動畫

    這篇文章主要為大家詳細介紹了Vue如何利用transition標簽實現(xiàn)頁面跳轉動畫,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起一下
    2023-08-08

最新評論