Vue 中 watch(監聽器)的使用方法

在 Vue 中,watch(監聽器)就像一個 “數據哨兵”,專門監聽響應式數據的變化。當被監聽的數據發生改變時,watch 會自動觸發預設的回調函數,我們可以在回調中執行自定義邏輯(比如發送請求、更新 DOM、執行計算等),是處理數據變化後副作用的核心工具。

一、基礎用法:監聽單個數據

最常用的場景是監聽單個響應式數據(ref 或 reactive),當數據變化時執行回調。

1. 監聽 ref 數據

<template>
  <div class="watch-demo">
    <h3>監聽ref數據</h3>
    <input v-model="username" placeholder="輸入用户名">
    <p>用户名變化日誌:{{ log }}</p>
  </div>
</template>

<script setup>
import { ref, watch } from 'vue'

// 被監聽的響應式數據
const username = ref('')
const log = ref('')

// 基礎監聽:監聽username的變化
watch(username, (newVal, oldVal) => {
  // newVal:變化後的值;oldVal:變化前的值
  log.value = `用户名從"${oldVal}"變為"${newVal}"`
})
</script>

這裏 watch 監聽username的變化,每次輸入框內容修改時,回調函數會接收新值(newVal)和舊值(oldVal),並更新日誌信息。

2. 監聽 reactive 對象的單個屬性

如果需要監聽 reactive 對象中的某個屬性,需通過 “字符串路徑” 或 “函數返回值” 的方式指定:

<template>
  <div>
    <h3>監聽reactive對象屬性</h3>
    <input v-model="user.age" type="number" placeholder="輸入年齡">
    <p>年齡變化提示:{{ tip }}</p>
  </div>
</template>

<script setup>
import { reactive, watch } from 'vue'

// reactive對象
const user = reactive({
  name: '張三',
  age: 25
})
const tip = ref('')

// 方式1:通過字符串路徑監聽單個屬性
watch('user.age', (newAge, oldAge) => {
  tip.value = `年齡從${oldAge}歲變為${newAge}歲`
})

// 方式2:通過函數返回值監聽(更推薦,支持複雜路徑)
// watch(() => user.age, (newAge, oldAge) => {
//   tip.value = `年齡從${oldAge}歲變為${newAge}歲`
// })
</script>

二、進階用法:監聽多個數據、深度監聽

1. 監聽多個數據

可以通過數組形式同時監聽多個響應式數據,任意一個數據變化都會觸發回調:

<template>
  <div>
    <h3>監聽多個數據</h3>
    <input v-model="a" type="number" placeholder="輸入數字a">
    <input v-model="b" type="number" placeholder="輸入數字b">
    <p>總和:{{ sum }}</p>
  </div>
</template>

<script setup>
import { ref, watch, computed } from 'vue'

const a = ref(0)
const b = ref(0)
const sum = ref(0)

// 監聽a和b的變化,任意一個變化都觸發
watch([a, () => b.value], ([newA, newB], [oldA, oldB]) => {
  sum.value = newA + newB
  console.log(`a從${oldA}變${newA},b從${oldB}變${newB}`)
})
</script>

回調函數的第一個參數是 “新值數組”,第二個參數是 “舊值數組”,順序與監聽的數組一致。

2. 深度監聽(監聽對象 / 數組內部變化)

默認情況下,watch 只監聽數據的 “引用變化”(比如給對象重新賦值、給數組重新賦值),不會監聽對象內部屬性或數組元素的變化。此時需要開啓deep: true實現深度監聽。

監聽 reactive 對象內部變化
<template>
  <div>
    <h3>深度監聽對象</h3>
    <input v-model="user.name" placeholder="修改姓名">
    <input v-model="user.age" type="number" placeholder="修改年齡">
    <p>用户信息變化:{{ userLog }}</p>
  </div>
</template>

<script setup>
import { reactive, watch } from 'vue'

const user = reactive({
  name: '張三',
  age: 25
})
const userLog = ref('')

// 深度監聽user對象(開啓deep: true)
watch(
  user,
  (newUser, oldUser) => {
    userLog.value = `姓名:${oldUser.name}→${newUser.name},年齡:${oldUser.age}→${newUser.age}`
  },
  { deep: true } // 開啓深度監聽
)
</script>

開啓deep: true後,無論是修改user.name還是user.age,都會觸發 watch 回調。

監聽數組元素變化
<template>
  <div>
    <h3>深度監聽數組</h3>
    <button @click="addItem">添加商品</button>
    <button @click="updateFirstItem">修改第一個商品</button>
    <ul>
      <li v-for="(item, index) in goods" :key="index">{{ item }}</li>
    </ul>
    <p>數組變化日誌:{{ goodsLog }}</p>
  </div>
</template>

<script setup>
import { ref, watch } from 'vue'

const goods = ref(['Vue教程', 'JS進階'])
const goodsLog = ref('')

// 深度監聽數組(數組元素變化時觸發)
watch(
  goods,
  (newGoods, oldGoods) => {
    goodsLog.value = `數組從[${oldGoods}]變為[${newGoods}]`
  },
  { deep: true }
)

// 添加數組元素
const addItem = () => {
  goods.value.push(`新商品${goods.value.length + 1}`)
}

// 修改數組元素
const updateFirstItem = () => {
  goods.value[0] = 'Vue 3實戰教程'
}
</script>

數組的push、splice、修改元素等操作,都會被深度監聽捕獲。

3. 立即執行(immediate: true)

默認情況下,watch 回調只在數據變化時觸發。如果需要 “初始化時立即執行一次”(比如頁面加載時就根據初始值發送請求),可以開啓immediate: true:

<template>
  <div>
    <h3>立即執行的watch</h3>
    <p>當前搜索關鍵詞:{{ keyword }}</p>
    <p>搜索結果:{{ result }}</p>
  </div>
</template>

<script setup>
import { ref, watch } from 'vue'

const keyword = ref('Vue')
const result = ref('')

// 開啓immediate:初始化時執行一次,之後關鍵詞變化時再執行
watch(
  keyword,
  async (newVal) => {
    // 模擬發送搜索請求
    result.value = `正在搜索"${newVal}"...`
    const mockRes = await new Promise((resolve) => {
      setTimeout(() => resolve(`找到${newVal}相關結果100條`), 500)
    })
    result.value = mockRes
  },
  { immediate: true }
)
</script>

頁面加載時,watch 會立即執行一次回調,根據初始關鍵詞 “Vue” 發送搜索請求,之後修改keyword時也會觸發搜索。

三、特殊用法:監聽對象的特定屬性(精準監聽)

如果只需要監聽對象的某個特定屬性(而非整個對象),可以通過 “函數返回值” 的方式精準監聽,無需開啓深度監聽(性能更優):

<template>
  <div>
    <h3>精準監聽對象屬性</h3>
    <input v-model="user.address.city" placeholder="修改城市">
    <p>城市變化:{{ cityLog }}</p>
  </div>
</template>

<script setup>
import { reactive, watch } from 'vue'

const user = reactive({
  name: '張三',
  address: {
    city: '北京',
    street: 'XX路'
  }
})
const cityLog = ref('')

// 精準監聽user.address.city,無需deep
watch(
  () => user.address.city, // 函數返回要監聽的屬性
  (newCity, oldCity) => {
    cityLog.value = `城市從${oldCity}變為${newCity}`
  }
)
</script>

這種方式只會監聽user.address.city的變化,修改user.name或user.address.street都不會觸發回調,性能比深度監聽更優。

四、watch 與 computed 的區別(關鍵!)

很多時候會混淆 watch 和 computed,核心區別如下:

特性 watch computed
核心用途 處理數據變化後的副作用(發請求、改 DOM、定時器等) 處理數據的衍生邏輯(數據計算、格式化、篩選)
觸發時機 數據變化時觸發(可配置立即執行) 依賴數據變化時自動重新計算
返回值 無返回值,靠回調執行邏輯 有返回值,可直接在模板中使用
緩存機制 無緩存,數據變化必觸發回調 有緩存,依賴不變時直接返回緩存結果
適用場景 數據變化後需要執行異步操作、複雜邏輯 簡單的衍生數據計算、模板中複雜表達式

示例對比:

  • 用 computed:根據用户名和密碼判斷是否可提交(衍生數據);

  • 用 watch:監聽表單提交狀態,變化時發送登錄請求(副作用)。

五、注意事項

  1. 避免監聽複雜對象:直接監聽大型對象或數組(開啓 deep)會影響性能,儘量採用 “精準監聽” 特定屬性;

  2. 清理副作用:如果 watch 回調中創建了定時器、事件監聽等,需要在組件卸載時清理,避免內存泄漏(可通過 watch 的返回函數實現):

watch(username, (newVal) => {
  const timer = setTimeout(() => {
    console.log('用户名變化:', newVal)
  }, 1000)
  // 返回清理函數,組件卸載時執行
  return () => clearTimeout(timer)
})
  1. 區分 ref 和 reactive 的監聽差異
    • 監聽 ref 數據:直接傳入 ref 對象(如watch(username, ...));
    • 監聽 reactive 對象屬性:需用函數返回(如watch(() => user.age, ...));
  1. 舊值的侷限性:監聽數組或對象時,oldVal 和 newVal 可能指向同一個引用(因為 Vue 不會深拷貝),如果需要對比舊值和新值,需手動深拷貝(如用JSON.parse(JSON.stringify(oldVal)))。

總結

watch 是 Vue 中處理數據變化副作用的核心工具,支持單個數據、多個數據、對象屬性、數組等多種監聽場景,還能通過deep、immediate等配置滿足複雜需求。它與 computed 分工明確:computed 負責 “數據衍生”,watch 負責 “副作用處理”。掌握 watch 的基礎用法、精準監聽、深度監聽和清理機制,能讓我們在數據變化時靈活控制邏輯,應對各類異步操作、DOM 更新等場景,讓代碼更健壯、更易維護。