Skip to main content

แนะนำ watch และ watchEffect

Document ต้นฉบับ: https://vuejs.org/guide/essentials/watchers.html

รู้จักกับ Watcher

ทีนี้ ขยายต่อจากเคสของ computed เราอาจจะมีเคสที่อยากดักจับ change ของตัวแปรของ reactive ไว้โดยเฉพาะ เช่น

  • ดักจับ ตัวแปรเอาไว้เพื่อคอยนำค่าไปเปลี่ยนแปลงให้อีกตัวหนึ่ง
  • ดักจับ เพื่อเมื่อค่าเปลี่ยนแปลงเราจะส่งค่าผ่าน API

นั่นคือ เคสที่เราจะดักจับ reactive และจะจัดการ "เปลี่ยนแปลง" สิ่งอื่นเพ่ิมเติมเข้ามา เคสเหล่านี้ Watcher จะช่วยได้

Watcher คือ function ที่สามารถตรวจจับ (observe) การเปลี่ยนแปลงของตัวแปร reactive ไว้ได้ ปกติเคสของ Watcher มักจะใช้กับเคสที่เกี่ยวข้องกับเหตุการณ์ที่เป็น asynchronous (เช่นการ call API) หรือการที่จะต้องส่งตัวแปรไป computed แบบหนักๆต่ออีกทีได้ (เช่น ส่งตัวแปรเพื่อไปแสดง DOM อันใหม่ออกมา)

ตัวอย่าง Watcher กับเคส ref ทั่วไป

เราจะมาลองดูตัวอย่าง Watcher เทียบกับ Computed กันก่อน

สมมุติเราจะรับ input เป็น string 1 ตัวแล้วเปลี่ยน string ตัวนั้นเป็นตัวพิมพ์ใหญ่ทั้งหมด

ถ้าเป็นเคส computed เราจะทำกันแบบนี้

<script setup>
import { ref, computed } from 'vue'
const message = ref('')
const uppercaseMessage = computed(() => {
return message.value.toUpperCase()
})
</script>

<template>
<div>
<input type="text" v-model="message">
<p>Uppercase Message: {{ uppercaseMessage }}</p>
</div>
</template>

ผลลัพธ์ก็จะเป็นแบบนี้

watch-01

ทีนี้ถ้าเป็น watch ละ

<script setup>
import { ref, watch } from 'vue'
const message = ref('')
const uppercaseMessage = ref('')

watch(message, (newMessage) => {
uppercaseMessage.value = newMessage.toUpperCase()
})
</script>

<template>
<div>
<input type="text" v-model="message">
<p>Uppercase Message: {{ uppercaseMessage }}</p>
</div>
</template>

ผลลัพธ์จะได้ออกมาเหมือนกัน

สังเกตนะครับว่า

  • ถ้าเป็น computed() เราจะใช้ตัวแปร computed ในการจัดการเลย
  • แต่ถ้าเป็น watch() เราจะเพียงดักจับการเปลี่ยนแปลงของตัวแปรนั้น เพื่อไปทำสิ่งอื่นแทน (เช่นเคสนี้คือนำไป update upperCase ใส่ตัวแปร uppercaseMessage แทน)

watch สามารถ watch ได้ทีละหลายตัวแปรเหมือนกันนะครับ เช่นเคสนี้ เป็นการ watch ตามตัวแปร 2 ตัวคือ x และ y และ

  • อันที่ 1: นำผลลัพธ์ที่รวมกันออกมาเป็นตัวแปร sum (แต่ก็เป็นแค่ sum ที่ใช้ใน function นั้น)
  • อันที่ 2: สามารถดูผลลัพธ์ของตัวแปร x, y ที่เปลี่ยนแปลงใหม่ได้ไปพร้อมกัน (newX, newY)
const x = ref(0)
const y = ref(0)

// getter
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)

// array of multiple sources (ขอแค่ตัวใดตัวหนึ่งเปลี่ยนแปลง = watch โดนเรียกหมด)
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})

Watcher กับ object

เคสควรระวังคือ watch นั้นใช้กับตัวแปร reactive ที่เป็น object ตรงๆไม่ได้ ต้องเรียกผ่าน getter ของ watch แทน

เช่นตัวอย่างเคสนี้

const obj = reactive({ count: 0 })

// จะทำงานไม่ได้ เพราะ watch จะมองไม่เห็น object ด้านในเปลี่ยนแปลง (จะส่งแค่ค่าเข้าไป)
watch(obj.count, (count) => {
console.log(`count is: ${count}`)
})

// instead, use a getter: (จะส่งตัว reactive เข้าไปได้)
watch(
() => obj.count,
(count) => {
console.log(`count is: ${count}`)
}
)

หรือถ้าจะใช้กับตัวแปรทั้งหมดของ object (เช็คว่า key ใด key หนึ่งของ object เปลี่ยนแปลง) = ให้เพิ่ม properties deep: true เข้าไปใน watcher แทน

เช่นตัวอย่างเคสนี้ เราจะทำ form รับ

  • ชื่อจริง (firstname)
  • นามสกุล (lastname)

โดยเราจะสร้าง object formData เข้ามารับทั้ง 3 ค่านี้เอาไว้ และเมื่อมีค่าใดค่าหนึ่งเปลี่ยนแปลง ให้ลอง console.log ออกมาดู (เอาแบบง่ายๆกันก่อน)

<script setup>
import { reactive, watch } from 'vue'
const formData = reactive({
firstname: '',
lastname: ''
})

watch(
() => formData,
(newValue, oldValue) => {
console.log('=== old')
console.log('firstname: ', oldValue.firstname, 'lastname: ', oldValue.lastname)
console.log('=== new')
console.log('firstname: ', newValue.firstname, 'lastname: ', newValue.lastname)
},
{ deep: true }
)
</script>

<template>
<div>
<div>
Firstname <input type="text" v-model="formData.firstname">
</div>
<div>
Lastname <input type="text" v-model="formData.lastname">
</div>
</div>
</template>

ผลลัพธ์

watch-02

(ถ้าอยากเทียบให้ลองเอา deep: true ออกดู จะเจอว่า watch จะไม่สามารถทำงานแบบนี้ได้)

แล้ว watchEffect ละคืออะไร ?

ทีนี้จากเคสข้างบน เราจะลองให้ code เหลือเท่านี้ ให้ console.log ออกมาตอนมี firstname, lastname ใหม่ใน formData เข้ามา

<script setup>
import { reactive, watch } from 'vue'
const formData = reactive({
firstname: '',
lastname: ''
})

watch(
() => formData,
(newValue, oldValue) => {
console.log('=== new name', `${newValue.firstname} ${newValue.lastname}`)
},
{ deep: true }
)
</script>

<template>
<div>
<div>
Firstname <input type="text" v-model="formData.firstname">
</div>
<div>
Lastname <input type="text" v-model="formData.lastname">
</div>
</div>
</template>

ทีนี้ถ้าเกิดว่ามันเริ่มมีหลายตัวแปร และเราเริ่มขี้เกียจมาไล่ใส่แต่ละตัวแปรละ = ใช้ watchEffect แทนได้

โดยการใช้ watchEffect นั้นไม่ต้องใส่ตัวแปรที่จะ watch แค่เรียกใช้ตัวแปรภายใน watchEffect ตัว watchEffect จะรู้ด้วยตัวเองได้เลยว่าต้อง watch ตามตัวไหนบ้าง (อย่างเคสนี้คือ firstname และ lastname)

<script setup>
import { reactive, watchEffect } from 'vue'
const formData = reactive({
firstname: '',
lastname: '',
fullname: ''
})

watchEffect(() => {
console.log('=== new name', `${formData.firstname} ${formData.lastname}`)
})
</script>

<template>
<div>
<div>Fullname: </div>
<div>
Firstname <input type="text" v-model="formData.firstname">
</div>
<div>
Lastname <input type="text" v-model="formData.lastname">
</div>
</div>
</template>

ผลลัพธ์ของทั้ง 2 code จะเหมือนกันดังนี้

watch-04

แต่ ! ถ้าทุกคนรัน 2 code นี้ไปพร้อมกัน จะเจอว่า

  • watch อันแรก console.log จะออกเมื่อมีการเปลี่ยนค่า
  • watchEffect จะ console.log ออกทันทีตอนเปิดขึ้นมา

watchEffect ได้เพิ่มคุณสมบัติหนึ่งของ watch เข้ามาคือ immediate เข้ามา นั่นคือจะเรียก watch นี้ก่อนครั้งแรกเสมอ โดยถ้าเราใส่ immediate: true ออกมาจะได้ผลลัพธ์เหมือนกันออกมาได้

import { reactive, watch } from 'vue'
const formData = reactive({
firstname: '',
lastname: '',
fullname: ''
})

watch(
() => formData,
(newValue, oldValue) => {
console.log('=== new name', `${newValue.firstname} ${newValue.lastname}`)
},
{ deep: true, immediate: true }
)

ตัวนี้จะมีประโยชน์มากตอนเราเรียกใช้กับพวก API หรือการเรียก data จากฝั่ง Server (จะไว้ลง detail ตอนใช้งานเพิ่มเติมตอนเรียก API)

เพราะฉะนั้นถ้าจะพิจารณาใช้ watch หรือ watchEffect ให้นึกถึง

  • ถ้าจะ track source ไหนที่ระบุโดยเฉพาะ = ใช้ watch
  • ถ้าจะ track การเปลี่ยนแปลงทั้ง set โดยอยากลำดับการจัดการข้อมูลให้ถูกต้อง = ใช้ watchEffect

ทั้ง 2 ตัวเดี๋ยวเราจะกลับมาอธิบายอีกทีตอนต่อ API