Vue 3的watchEffect、watch卸夕、computed API詳解

watchEffect

執(zhí)行監(jiān)聽

watchEffect比較奇特快集,它跟Vue 2的watch有所區(qū)別,它的寫法是:

watchEffect(() => {
  // 執(zhí)行一些操作个初,其中必須含有響應(yīng)式變量
})

為什么感覺怪怪的院溺?watchEffect并沒有要求你聲明被監(jiān)聽的變量,而是逐虚,你在執(zhí)行體里寫哪個(gè)變量谆膳,Vue就收集、監(jiān)聽哪個(gè)變量买雾,而且可以同時(shí)監(jiān)聽多個(gè)變量漓穿,看下例:

<template>
  <div>
    <button @click="r++">{{ r }}</button>
    <button @click="s.a++">{{ s.a }}</button>
    <button @click="s.b++">{{ s.b }}</button>
    <button @click="s.a++;s.b++">{{ s.a }} - {{ s.b }}</button>
  </div>
</template>

<script>
import { ref, computed, watchEffect } from 'vue';
export default {
  setup() {
    let r = ref(10);
    watchEffect(() => {
      console.log(r.value);
    });
    let s = ref({a: 100, b: 200});
    watchEffect(() => {
      console.log('a:', s.value.a);
    });
    watchEffect(() => {
      console.log('b:', s.value.b);
    });
    watchEffect(() => {
      console.log('a - b:', s.value.a + '-' + s.value.b);
    });
    watchEffect(() => {
      console.log('value:', s.value);
    });
    return {
      r,s
    };
  },
};
</script>

可以看到:

  1. 首先器净,watchEffect是立即執(zhí)行的当凡,所以組件初始化的時(shí)候就全部執(zhí)行了一遍。

  2. 點(diǎn)擊button1浪慌,打印10权纤,很好理解乌妒。

  3. s的傳入值是個(gè)對(duì)象,button2修改的是屬性a古掏,那么槽唾,只有監(jiān)聽屬性a的監(jiān)聽器才會(huì)有反應(yīng),只跟屬性b相關(guān)的監(jiān)聽是不會(huì)有反應(yīng)的庞萍,只監(jiān)聽s.value的監(jiān)聽器也不會(huì)有反應(yīng)钝计。點(diǎn)擊button3和button4也會(huì)印證這個(gè)結(jié)論。

  4. 在watchEffect里操作響應(yīng)式數(shù)據(jù)债沮,不會(huì)引起無(wú)限循環(huán)監(jiān)聽践付,這雖然很顯而易見永高,但是也在此說一句提针。

  5. 多個(gè)watchEffect的執(zhí)行順序是watchEffect的書寫順序。

  6. watchEffect拿不到更新前的值饲宛,這一點(diǎn)要注意艇抠。

停止監(jiān)聽

  1. 自動(dòng)停止

先說watchEffect生命周期的開始久锥,是從組件的setup()函數(shù)或生命周期鉤子被調(diào)用時(shí)開始。自動(dòng)停止是在組件卸載時(shí)自動(dòng)停止絮重。

  1. 手動(dòng)停止

將watchEffect賦值給變量青伤,執(zhí)行這個(gè)變量即可手動(dòng)停止殴瘦。比如:

const xx = watchEffect(() => {
  console.log('a:', s.value.a);
  s.value.a += 10
});
// 后來(lái)某個(gè)時(shí)間執(zhí)行了:
xx(); // 停止監(jiān)聽

清除副作用

官方文檔:https://v3.cn.vuejs.org/guide/reactivity-computed-watchers.html#清除副作用

官方文檔里偶爾會(huì)蹦出來(lái)一個(gè)詞“副作用”,初學(xué)者看完一頭霧水擎厢,什么鬼副作用?英文文檔里副作用是Side Effect芬探,到底什么意思厘惦?

純函數(shù)里的副作用概念

副作用其實(shí)是一個(gè)比較生僻的概念宵蕉,最早來(lái)自于“純函數(shù)”,純函數(shù)是編程界早期的一個(gè)概念别智,具體可以看(https://zhuanlan.zhihu.com/p/139659155https://juejin.cn/post/6950059795659358221)稼稿。純函數(shù)特征:

它應(yīng)始終返回相同的值让歼。不管調(diào)用該函數(shù)多少次,無(wú)論今天硬猫、明天還是將來(lái)某個(gè)時(shí)候調(diào)用它改执。
自包含(不使用全局變量)辈挂。
它不應(yīng)修改程序的狀態(tài)或引起副作用(修改全局變量)。

注意看冕香,這里就出現(xiàn)了“副作用”后豫。所以挫酿,純函數(shù)的副作用就是:

一個(gè)函數(shù)除了返回確定的值之外,還做了其他的事情惫霸,那么這個(gè)函數(shù)做的這些事情就叫做“副作用”。

比如console.log(123)就有副作用猜丹,它返回undefined是主作用硅卢,但是我們不需要它的主作用,它的副作用就是在控制臺(tái)打印123脉顿,我們要的是它的副作用点寥。

再比如:

let counter = 0;

// 有副作用,副作用是把外部變量改了
incCounter() {
    counter += 1;
    return counter;
}

// 沒有副作用
incNumber(m) {
    return m + 1;
}
React中的副作用概念

React等框架早先就在使用這個(gè)詞蔽莱,Vue從3.0開始碾褂,在文檔里出現(xiàn)這個(gè)詞历葛。

那么嘀略,在React中,是不是副作用也是這個(gè)定義呢咒程?未必帐姻∧潭危看看React是怎么說的:

你之前可能已經(jīng)在 React 組件中執(zhí)行過數(shù)據(jù)獲取、訂閱或者手動(dòng)修改過 DOM呢铆。我們統(tǒng)一把這些操作稱為“副作用”棺克,或者簡(jiǎn)稱為“作用”。
useEffect 就是一個(gè) Effect Hook娜谊,給函數(shù)組件增加了操作副作用的能力。它跟 class 組件中的 componentDidMount拇惋、componentDidUpdatecomponentWillUnmount 具有相同的用途撑帖,只不過被合并成了一個(gè) API澳眷。
例如,下面這個(gè)組件在 React 更新 DOM 后會(huì)設(shè)置一個(gè)頁(yè)面標(biāo)題:

import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  // 相當(dāng)于 componentDidMount 和 componentDidUpdate:
  useEffect(() => {
    // 使用瀏覽器的 API 更新頁(yè)面標(biāo)題
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

當(dāng)你調(diào)用 useEffect 時(shí)衷敌,就是在告訴 React 在完成對(duì) DOM 的更改后運(yùn)行你的“副作用”函數(shù)缴罗。由于副作用函數(shù)是在組件內(nèi)聲明的面氓,所以它們可以訪問到組件的 props 和 state蛆橡。默認(rèn)情況下,React 會(huì)在每次渲染后調(diào)用副作用函數(shù) —— 包括第一次渲染的時(shí)候呻拌。
副作用函數(shù)還可以通過返回一個(gè)函數(shù)來(lái)指定如何“清除”副作用藐握。

看完了之后垃喊,我們就知道React對(duì)于副作用的解釋:

在執(zhí)行所有業(yè)務(wù)邏輯之前,也就是組件初始化的時(shí)候抬闷,組件根據(jù)開發(fā)者的設(shè)定,由自身驅(qū)動(dòng)的第一次DOM修改评架,就是主作用纵诞。此時(shí)培遵,組件還沒有執(zhí)行任何一行邏輯代碼。

主作用之后嗡呼,組件就開始執(zhí)行用戶邏輯了南窗,這里你眼里的業(yè)務(wù)邏輯代碼郎楼,在React眼里都是副作用。

Vue 3里的副作用概念

跟React應(yīng)該是說的一個(gè)意思敌买,具體說:

所以首先了解一下“主作用”虹钮,在Vue世界里荐操,視圖層和DOM層是兩碼事珍策,盡管一些初級(jí)程序員認(rèn)為它們是一碼事。變更響應(yīng)式數(shù)據(jù)的主作用就是變更后的數(shù)據(jù)能渲染到視圖層屯耸。前端還有比這個(gè)事更重要的事嗎蹭劈?沒有吧铺韧。

副作用就是響應(yīng)式數(shù)據(jù)的變更造成的其他連鎖反應(yīng),以及后續(xù)邏輯塔逃,這些連鎖反應(yīng)都叫副作用。在藥物學(xué)里伏蚊,副作用往往是不良反應(yīng)躏吊,但是在Vue 3里并不是帐萎。上面標(biāo)題里說“清除副作用”,也并不是說因?yàn)楦弊饔檬遣涣挤磻?yīng)所以要清除凳怨,而是Vue 3提供一個(gè)方法讓你隨時(shí)可以取消副作用肤舞。

副作用主要有:

  1. DOM更新

  2. watchEffect

  3. watch

  4. computed

  5. ...

你沒看錯(cuò)均蜜,既然更新視圖層才是主作用,那么視圖層更新到DOM上在Vue眼里是副作用篙顺,而且德玫,變更響應(yīng)式數(shù)據(jù)觸發(fā)執(zhí)行computed和觸發(fā)執(zhí)行watchEffect當(dāng)然也是副作用椎麦。所以watchEffect本身就是副作用。

清除副作用是什么意思

那么官方文檔說的“清除副作用”到底在說什么琴儿?它意思是說造成,如果有些副作用是異步的雄嚣,這就意味著你可以取消它,那么Vue創(chuàng)始人就給你提供了一個(gè)方法鼓鲁,讓你優(yōu)雅的取消這些異步副作用。

比如你有一個(gè)頁(yè)碼組件尺棋,里面有5個(gè)頁(yè)碼膘螟,點(diǎn)擊就會(huì)異步請(qǐng)求數(shù)據(jù)碾局。于是我就做了一個(gè)監(jiān)聽,監(jiān)聽當(dāng)前頁(yè)碼内斯,只要有變化就ajax一次俘闯。下例是不可直接運(yùn)行的演示代碼:

    let content = '';
    const pageNumber = ref(1);
    function onClickPageNumber(val) {
      pageNumber.value = val;
    }
    watchEffect(() => {
      ajax({pageNumber}).then(response => {
        content = response.data;
      })
    });

現(xiàn)在問題是忽冻,如果我點(diǎn)擊的比較快,從1到5全點(diǎn)了一遍遮婶,那么會(huì)有5個(gè)ajax請(qǐng)求旗扑,最終頁(yè)面會(huì)顯示第幾頁(yè)的內(nèi)容慈省?你說第5頁(yè)?那你是假定請(qǐng)求第5頁(yè)的ajax響應(yīng)的最晚清钥,事實(shí)呢?并不一定缕坎。于是這就會(huì)導(dǎo)致錯(cuò)亂。還有一個(gè)問題匾寝,我連續(xù)快速點(diǎn)5次頁(yè)碼,等于我并不想看前4頁(yè)的內(nèi)容急凰,那么是不是前4次的請(qǐng)求都屬于帶寬浪費(fèi)抡锈?這也不好乔外。于是官方就給出了一種解決辦法:

首先,你的異步操作必須是能中止的異步操作撇簿,對(duì)于定時(shí)器來(lái)講中止定時(shí)器很容易四瘫,clearInterval之類的就可以欲逃,但對(duì)于ajax來(lái)講,需要借助ajax庫(kù)(比如axios)提供的中止ajax辦法來(lái)中止ajax∏妈荆現(xiàn)在我寫一個(gè)能直接運(yùn)行的范例演示一下中止異步操作:

我先搭建一個(gè)最簡(jiǎn)Node服務(wù)器竭望,3300端口的:

    const http = require('http');
    const server = http.createServer((req, res) => {
      res.setHeader('Access-Control-Allow-Origin', "*");
      res.setHeader('Access-Control-Allow-Credentials', true);
      res.setHeader('Access-Control-Allow-Methods', 'POST, GET, PUT, DELETE, OPTIONS');
      res.writeHead(200, {
        'Content-Type': 'application/json'
      });
    });
    server.listen(3300, () => {
      console.log('Server is running...');
    });
    server.on('request', (req, res) => {
      setTimeout(() => {
        if (/\d.json/.test(req.url)) {
          const data = {
            content: '我是內(nèi)容裕菠,來(lái)自' + req.url
          }
          res.end(JSON.stringify(data));
        }
      }, Math.random() * 2000);
    });

清除副作用的核心有2點(diǎn):

  1. 異步副作用要給出取消自身的辦法

  2. watchEffect提供取消副作用的接口奴潘,也就是onInvalidate方法。Invalidate中文譯義是作廢掘剪,onInvalidate也就是作廢監(jiān)聽器奈虾。

<template>
  <div>
    <div>content: {{ content }}</div>
    <button @click="pageNumber = (pageNumber++ % 5) + 1">{{ pageNumber }}</button>
  </div>
</template>

<script>
import axios from 'axios';
import { ref, watchEffect } from 'vue';
export default {
  setup() {
    let pageNumber = ref(1);
    let content = ref('');

    watchEffect((onInvalidate) => {
      // const CancelToken = axios.CancelToken;
      // const source = CancelToken.source();
      // onInvalidate(() => {
      //   source.cancel();
      // });
      axios
        .get(`http://localhost:3300/${pageNumber.value}.json`, {
          // cancelToken: source.token,
        })
        .then((response) => {
          content.value = response.data.content;
        })
        .catch(function (err) {
          if (axios.isCancel(err)) {
            console.log('Request canceled', err.message);
          }
        });
    });
    return {
      pageNumber,
      content,
    };
  },
};
</script>

上面注釋掉的代碼先保持注釋掉,然后我們經(jīng)過20多次瘋狂點(diǎn)擊之后蜡塌,得到這個(gè)結(jié)果勿负,顯然,內(nèi)容錯(cuò)亂了:

image.png

現(xiàn)在我取消注釋琅摩,重新20多次瘋狂點(diǎn)擊迫吐,得到的結(jié)果就正確了:

image.png

除了最后一個(gè)請(qǐng)求志膀,上面那些請(qǐng)求有2種結(jié)局:

  1. 一種是響應(yīng)的太快鳖擒,來(lái)不及取消的請(qǐng)求,這種請(qǐng)求會(huì)返回200戳稽,不過既然它響應(yīng)太快期升,沒有任何一次后續(xù)ajax能夠來(lái)得及取消它,說明任何一次后續(xù)ajax開始之前颂郎,它就已經(jīng)結(jié)束了乓序,那么它一定會(huì)被后續(xù)某些請(qǐng)求所覆蓋坎背,所以這類請(qǐng)求的content會(huì)顯示一瞬間,然后被后續(xù)的請(qǐng)求覆蓋陨献,絕對(duì)不會(huì)比后面的請(qǐng)求還晚耿戚。

  2. 另一種就是紅色的那些被取消的請(qǐng)求阿趁,因?yàn)轫憫?yīng)的慢,所以被取消掉了皂股。

所以最終結(jié)果一定是正確的,而且節(jié)省了很多帶寬就斤,也節(jié)省了系統(tǒng)開銷蘑辑。

這就是官方說的“清除副作用”。清除定時(shí)器更簡(jiǎn)單绷旗,我不舉例了衔肢。

副作用刷新時(shí)機(jī)

官方文檔:https://v3.cn.vuejs.org/guide/reactivity-computed-watchers.html#副作用刷新時(shí)機(jī)

官方文檔里的“副作用刷新時(shí)機(jī)”更晦澀豁翎,我解釋一下。

Vue 的響應(yīng)性系統(tǒng)會(huì)緩存副作用函數(shù)邦尊,并異步地刷新它們蝉揍,這樣可以避免同一個(gè)“tick”中多個(gè)狀態(tài)改變導(dǎo)致的不必要的重復(fù)調(diào)用匙隔。

同一個(gè)“tick”的意思是,Vue的內(nèi)部機(jī)制會(huì)以最科學(xué)的計(jì)算規(guī)則將視圖刷新請(qǐng)求合并成一個(gè)一個(gè)的"tick"捍掺,每個(gè)“tick”刷新一次視圖再膳,比如a=1;b=2;只會(huì)觸發(fā)一次視圖刷新喂柒。$nextTick的Tick就是指這個(gè)禾嫉。

繼續(xù)說熙参,比如有個(gè)watchEffect監(jiān)聽了2個(gè)變量a和b麦备,我的業(yè)務(wù)寫了a=1;b=2;,你覺得監(jiān)聽器會(huì)調(diào)用2次黍匾?當(dāng)然不會(huì)呛梆,Vue會(huì)合并成1次去執(zhí)行填物,代碼如下,console.log只會(huì)執(zhí)行一次:

<template>
  <div>
    <button
      @click="
        r++;
        s++;
      "
    >
      {{ r }} - {{ s }}
    </button>
  </div>
</template>

<script>
import { ref, watchEffect } from 'vue';
export default {
  setup() {
    let r = ref(2);
    let s = ref(10);
    watchEffect(() => {
      console.log(r.value, s.value);
    });
    return {
      r,
      s,
    };
  },
};
</script>

在核心的具體實(shí)現(xiàn)中壶笼,組件的update函數(shù)也是一個(gè)被偵聽的副作用雁刷。當(dāng)一個(gè)用戶定義的副作用函數(shù)進(jìn)入隊(duì)列時(shí)沛励,默認(rèn)情況下,會(huì)在所有的組件update前執(zhí)行坤候。

所謂組件的update函數(shù)是Vue內(nèi)置的用來(lái)更新DOM的函數(shù)企蹭,它也是副作用,上文已經(jīng)提到過徒河。這時(shí)候有一個(gè)問題顽照,就是默認(rèn)下闽寡,Vue會(huì)先執(zhí)行組件DOM update尼酿,還是先執(zhí)行監(jiān)聽器裳擎?測(cè)一下:

<template>
  <div>
    <button
      id="aa"
      @click="
        r++;
        s++;
      "
    >
      {{ r }} - {{ s }}
    </button>
  </div>
</template>

<script>
import { ref, watchEffect } from 'vue';
export default {
  setup() {
    let r = ref(2);
    let s = ref(10);
    watchEffect(
      () => {
        console.log(r.value, s.value);
        console.log(document.querySelector('#aa') && document.querySelector('#aa').innerText);
      }
    );
    return {
      r,
      s,
    };
  },
};
</script>

點(diǎn)擊若干次(比如2次)按鈕句惯,得到的結(jié)果是:

image.png
image.png

為什么點(diǎn)之前按鈕的innerText打印null支救?因?yàn)槭聦?shí)就是默認(rèn)先執(zhí)行監(jiān)聽器各墨,然后更新DOM启涯,此時(shí)DOM還未生成,當(dāng)然是null黎做。

當(dāng)我第1和2次點(diǎn)擊完蒸殿,你會(huì)發(fā)現(xiàn)鸣峭,document.querySelector('#aa').innerText獲取到的總是點(diǎn)擊之前DOM的內(nèi)容。這也說明爬骤,默認(rèn)Vue先執(zhí)行監(jiān)聽器莫换,所以取到了上一次的內(nèi)容,然后執(zhí)行組件update坷剧。

Vue 2其實(shí)也是這種機(jī)制听隐,Vue 2使用this.$nextTick()去獲取組件更新完成之后的DOM哄啄,在watchEffect里就不需要用this.$nextTick()(也沒法用)风范,有一個(gè)辦法能獲取組件更新完成之后的DOM硼婿,就是使用:

watchEffect(
  () => {
    /* ... */
  },
  {
    flush: 'post'
  }
)

現(xiàn)在設(shè)上flush配置項(xiàng)禽车,重新進(jìn)入組件,再看看:

沒設(shè)flush: 'post' 設(shè)了flush: 'post'
image.png
image.png

所以結(jié)論是州胳,如果要操作“更新之后的DOM”栓撞,就要配置flush: 'post'碗硬。

watch

Vue 3 watch與Vue 2 watch對(duì)比

  1. Vue 3 watch與Vue 2的實(shí)例方法vm.$watch(也就是this.$watch)的基本用法差不多,只不過程序員大多使用watch配置項(xiàng)恩尾,可能對(duì)$watch實(shí)例方法不太熟。實(shí)例方法的一個(gè)優(yōu)勢(shì)是更靈活木人,第一個(gè)參數(shù)可以接受一個(gè)函數(shù)猎物,等于是接受了一個(gè)getter函數(shù)。
<template>
  <div>
    <button @click="r++">{{ r }}</button>
  </div>
</template>

<script>
import { ref, watch } from 'vue';
export default {
  setup() {
    let r = ref(1);
    let s = ref(10);
    watch(
      () => r.value + s.value,
      (newVal, oldVal) => {
        console.log(newVal, oldVal);
      }
    );
    return {
      r,
      s,
    };
  },
};
</script>
  1. Vue 3 watch增加了同時(shí)監(jiān)聽多個(gè)變量的能力淘讥,用數(shù)組表達(dá)要監(jiān)聽的變量蒲列〔蟀眨回調(diào)參數(shù)是這種結(jié)構(gòu):[newR, newS, newT], [oldR, oldS, oldT],不要理解成其他錯(cuò)誤的結(jié)構(gòu)抵赢。
<template>
  <div>
    <button @click="r++">{{ r }}</button>
  </div>
</template>

<script>
import { ref, watch } from 'vue';
export default {
  setup() {
    let r = ref(1);
    let s = ref(10);
    let t = ref(100);
    watch(
      [r, s, t],
      ([newR, newS, newT], [oldR, oldS, oldT]) => {
        console.log([newR, newS, newT], [oldR, oldS, oldT]);
      }
    );
    return {
      r,
    };
  },
};
</script>
  1. 被監(jiān)聽的變量必須是:A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.也就是說铅鲤,可以是getter/effect函數(shù)、ref鹏往、Proxy以及它們的數(shù)組。絕對(duì)不可以是純對(duì)象或基本數(shù)據(jù)伊履。

  2. 想要Vue 3的watch立即執(zhí)行款违,可以在watch的最后一個(gè)參數(shù)寫上{immediate: true}

  3. Vue 3的深度監(jiān)聽還有沒有介褥?當(dāng)然有,而且默認(rèn)就是溢陪,無(wú)需聲明。當(dāng)然杉编,前提是深層property也是響應(yīng)式的咆霜。如果深層property無(wú)響應(yīng)式,那么即便寫上{deep: true}也沒用光酣。

Vue 3 watch與Vue 3 watchEffect的差異

這方面官方文檔說的還可以:

  • 惰性地執(zhí)行副作用脉课,也就是說不會(huì)立即執(zhí)行一次;
  • 更具體地說明應(yīng)觸發(fā)偵聽器重新運(yùn)行的狀態(tài)唱遭,這句話翻譯還是很晦澀呈驶,其實(shí)意思是說,你現(xiàn)在能一眼看出來(lái)哪個(gè)變量被監(jiān)聽订晌;
  • 能訪問偵聽狀態(tài)的先前值和當(dāng)前值蚌吸,不要小看這個(gè)差別,有時(shí)候拿不到先前值就沒法進(jìn)行業(yè)務(wù)奕枢。

所以佩微,當(dāng)你不希望立即執(zhí)行一次監(jiān)聽器,或者需要拿到先前值谷浅,或者想明確表明哪些變量被監(jiān)聽了奶卓,就用watch。

其他差異有:

  1. 如果監(jiān)聽一個(gè)Proxy變量p,它的內(nèi)部值結(jié)構(gòu)是{a: {b: {c: 2}}}{a: {b: {c: {d: 3}}}}盏浙,我打算監(jiān)聽p.a.b.c,那么:
watchEffect watch且p.a.b.c是基本類型 watch且p.a.b.c是引用類型
必須監(jiān)聽p.a.b.c自身 必須監(jiān)聽p.a.b.c的任意一級(jí)上級(jí)property 監(jiān)聽p.a.b.c自身和任意上級(jí)property均可
  1. 如果監(jiān)聽ref竹海,跟上面類似斋配,只是有2個(gè)注意事項(xiàng):一是p后面不要忘記加.value孵稽,二是所謂“p.value.a.b.c的任意上級(jí)property”最高只允許到p.value,不能到p园细。

Vue 3 watch與Vue 3 watchEffect的共性

官方說接校,watch也有停止偵聽狮崩,清除副作用睦柴、副作用刷新時(shí)機(jī)和偵聽器調(diào)試行為毡熏。簡(jiǎn)單舉例:

  1. watch停止監(jiān)聽:

停止監(jiān)聽watch很簡(jiǎn)單痢法,watch的時(shí)候就必須賦值給一個(gè)變量,這時(shí)候就開始監(jiān)聽蘸炸。想停止監(jiān)聽就把這個(gè)變量當(dāng)函數(shù)執(zhí)行一下尖奔。

<template>
  <div>
    <button @click="r++">{{ r }}</button>
    <button @click="s()">stop</button>
  </div>
</template>

<script>
import { ref, watch } from 'vue';
export default {
  setup() {
    let r = ref(2);
    let s = watch(r, () => {
      console.log(r.value);
    });
    return {
      r,
      s,
    };
  },
};
</script>
  1. watch清除副作用:
<template>
  <div>
    <div>content: {{ content }}</div>
    <button @click="pageNumber = (pageNumber++ % 5) + 1">{{ pageNumber }}</button>
  </div>
</template>

<script>
import axios from 'axios';
import { ref, watch } from 'vue';
export default {
  setup() {
    let pageNumber = ref(1);
    let content = ref('');

    watch(pageNumber, (newVal, oldVal, onInvalidate) => {
      const CancelToken = axios.CancelToken;
      const source = CancelToken.source();
      onInvalidate(() => {
        source.cancel();
      });
      axios
        .get(`http://localhost:3300/${pageNumber.value}.json`, {
          cancelToken: source.token,
        })
        .then((response) => {
          content.value = response.data.content;
        })
        .catch(function (err) {
          if (axios.isCancel(err)) {
            console.log('Request canceled', err.message);
          }
        });
    });
    return {
      pageNumber,
      content,
    };
  },
};
</script>
  1. 調(diào)整副作用刷新時(shí)機(jī)提茁,可以嘗試注釋flush: 'post',作為對(duì)比:
<template>
  <div>
    <button
      id="aa"
      @click="
        r++;
        s++;
      "
    >
      {{ r }} - {{ s }}
    </button>
  </div>
</template>

<script>
import { ref, watch } from 'vue';
export default {
  setup() {
    let r = ref(2);
    let s = ref(10);
    watch(r,
      () => {
        console.log(r.value, s.value);
        console.log(document.querySelector('#aa') && document.querySelector('#aa').innerText);
      },
      {
        flush: 'post'
      }
    );
    return {
      r,
      s,
    };
  },
};
</script>

computed

Vue 3跟Vue 2的computed的差別在于稀拐,Vue 2是所有計(jì)算屬性都是根對(duì)象的屬性,Vue 3是計(jì)算屬性都是獨(dú)立變量铲咨,其他區(qū)別很小,就不細(xì)說了坯苹。

Vue 3 computed特點(diǎn):

  • computed默認(rèn)接收getter函數(shù)摇天,也可以接收一個(gè)對(duì)象,對(duì)象里有g(shù)et和set方法为鳄。set方法接收一個(gè)val參數(shù)腕让。初學(xué)者可能會(huì)忘記寫getter函數(shù),只寫計(jì)算表達(dá)式偏形,要注意這點(diǎn)。

  • computed一定返回ref對(duì)象队橙,所以并不需要在計(jì)算函數(shù)里給返回值添加響應(yīng)式萨惑,這屬于畫蛇添足。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末吹由,一起剝皮案震驚了整個(gè)濱河市倾鲫,隨后出現(xiàn)的幾起案子萍嬉,更是在濱河造成了極大的恐慌,老刑警劉巖磕道,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件溺蕉,死亡現(xiàn)場(chǎng)離奇詭異悼做,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)漓雅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門朽色,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人抱冷,你說我怎么就攤上這事腾誉【唬” “怎么了瘦癌?”我有些...
    開封第一講書人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵讯私,是天一觀的道長(zhǎng)西傀。 經(jīng)常有香客問我,道長(zhǎng)娘锁,這世上最難降的妖魔是什么饺鹃? 我笑而不...
    開封第一講書人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮镊屎,結(jié)果婚禮上茄螃,老公的妹妹穿的比我還像新娘。我一直安慰自己用狱,他們只是感情好凄吏,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開白布沦补。 她就那樣靜靜地躺著授账,像睡著了一般医男。 火紅的嫁衣襯著肌膚如雪镀梭。 梳的紋絲不亂的頭發(fā)上踱启,一...
    開封第一講書人閱讀 52,328評(píng)論 1 310
  • 那天研底,我揣著相機(jī)與錄音榜晦,去河邊找鬼羽圃。 笑死,一個(gè)胖子當(dāng)著我的面吹牛识窿,可吹牛的內(nèi)容都是我干的脑融。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼甥温,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼膜宋!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起史简,我...
    開封第一講書人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤圆兵,失蹤者是張志新(化名)和其女友劉穎枢贿,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體超凳,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡耀态,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年首装,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驰吓。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖檬贰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情庄蹋,我是刑警寧澤迷雪,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站倦西,受9級(jí)特大地震影響赁严,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜卤档,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一程剥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧舔腾,春花似錦搂擦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至徘钥,卻和暖如春呈础,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背而钞。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留撬陵,地道東北人网缝。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓粉臊,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親扼仲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容