[vue] Vue3 課程學習筆記
說明
記錄一下 Vue3 課程學習筆記
ref & reactive
- 如果需要一個基本數據類型的響應式數據,使用
ref
。 - 如果需要一個物件類型的響應式數據,使用
reactive
。 - 如果需要一個物件類型的響應式數據,並且結構較深層,使用
reactive
(表單結構等)。 (但我還是都 ref 到底)
在下方我們使用 reactive 來創建一個物件 car
,並透過 changeCarName
和 changeCar
來改變 car
的屬性。
但 changeCar
的時候,car
的屬性會被重新賦值,這時候 car
的屬性就會變成新的物件,而不是原來的物件,也就表示 car
的屬性不再是響應式的。
當你使用 reactive 定義一個物件時,Vue 會將該物件的屬性進行代理(proxy)。
只有修改這個物件內部的屬性時
,Vue 才能捕捉變更並觸發相應的更新。
如果你直接將 car 變數重新指向另一個物件,這並不會影響原來的代理,也就無法觸發 Vue 的響應更新機制。
所以當你有非常多屬性要改變時,需要使用 Object.assign
來改變 car
的屬性。
<script setup>
import { ref, reactive } from "vue";
let car = reactive({
name: "BMW",
price: 100000,
});
function changeCarName(newName) {
car.name = newName;
}
function changeCar() {
car = {
name: "Audi",
price: 200000,
};
}
function correctChangeCar() {
car.name = "Audi";
car.price = 200000;
// OR
Object.assign(car, {
name: "Audi",
price: 200000,
});
}
</script>
<template>
<header>
<h1>{{ car.name }}</h1>
<h1>{{ car.price }}</h1>
<button @click="changeCarName('Audi')">Change Car Name</button>
<button @click="changeCar">Wrong Change Car</button>
<button @click="correctChangeCar">Correct Change Car</button>
</header>
</template>
toRefs & toRef
- 解構賦值的影響:
let { name, age } = person
的操作只是將 person 物件中的值解構並複製給變數name
和age
,這些變數之後與 person 再無關聯,也就是他們只是普通的變數,而不是響應式的ref
或reactive
。
<script setup>
import { reactive } from 'vue'
let person = reactive({
name: 'John',
age: 20,
})
let { name, age } = person
function changeName(newName) {
name = newName
}
function changeAge(newAge) {
age = newAge
}
</script>
<template>
<header>
<h1>{{ name }}</h1>
<h1>{{ age }}</h1>
<button @click="changeName('Jane')">Change Name</button>
<button @click="changeAge(21)">Change Age</button>
</header>
</template>
所以要解決這個問題,我們需要使用 toRefs
或 toRef
來將 person
物件中的屬性轉換為響應式的 ref
或 reactive
。
toRefs
會將person
物件中的所有屬性
轉換為響應式的ref
。toRef
會將person
物件中的指定屬性
轉換為響應式的ref
。
<script setup>
import { reactive, toRefs, toRef } from 'vue'
let person = reactive({
name: 'John',
age: 20,
})
let { name, age } = toRefs(person)
// or
// let name = toRef(person, 'name')
// let age = toRef(person, 'age')
function changeName(newName) {
name.value = newName
}
function changeAge(newAge) {
age.value = newAge
}
</script>
computed
如果要把 firstName 的首字母大寫,可以這樣寫:
<script setup>
import { ref } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
</script>
<template>
<header>
<input v-model="firstName" />
<input v-model="lastName" />
<p>{{ firstName.slice(0, 1).toUpperCase() + firstName.slice(1) }} - {{ lastName }}</p>
</header>
</template>
但這樣寫的話會不好維護,我們需要讓 template 保持簡潔,所以可以使用 computed 來寫,當 firstName 和 lastName 改變時,computed 會自動更新:
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed(() => `${firstName.value.slice(0, 1).toUpperCase() + firstName.value.slice(1)} - ${lastName.value}`)
</script>
<template>
<header>
<input v-model="firstName" />
<input v-model="lastName" />
<p>{{ fullName }}</p>
</header>
</template>
計算屬性只會計算一次,所以當你多次使用時,會得到相同的結果,並不會重新計算。
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed(() => `${firstName.value.slice(0, 1).toUpperCase() + firstName.value.slice(1)} - ${lastName.value}`)
</script>
<template>
<header>
<input v-model="firstName" />
<input v-model="lastName" />
<p>{{ fullName }}</p>
<p>{{ fullName }}</p>
<p>{{ fullName }}</p>
<p>{{ fullName }}</p>
<p>{{ fullName }}</p>
</header>
</template>
如果需要直接修改
計算屬性,可以在 computed 中使用 set
方法。
如果不這麼寫,直接修改 fullName
的話,會得到 [Vue warn] Write operation failed: computed value is readonly
錯誤。
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
get: () => `${firstName.value} ${lastName.value}`,
set: (newValue) => {
[firstName.value, lastName.value] = newValue.split(' ')
}
})
</script>
watch
使用 watch 的時候,也會給 stop 方法,可以停止監聽。
const stopWatch = watch(firstName, (newVal, oldVal) => {
if (newVal === "John") {
stopWatch();
}
});
reactive 和 ref 的 watch
這兩種的 watch 的差別在於,reactive 預設是深層(deep)監聽且無法關閉深層監聽,ref 預設是淺層(shallow)監聽。
<script setup>
import { watch, ref, reactive } from "vue";
const person1 = ref({
name: "John",
age: 20,
});
const person2 = reactive({
name: "John",
age: 20,
});
watch(person1, (newVal, oldVal) => {
console.log(`firstName changed from ${oldVal} to ${newVal}`);
});
watch(person2, (newVal, oldVal) => {
console.log(`firstName changed from ${oldVal} to ${newVal}`);
});
</script>
監聽物件的屬性
如果需要監聽物件的屬性,可以使用 () => person.value.name
來監聽。
<script setup>
import { reactive, watch } from 'vue'
const person = reactive({
name: 'John',
age: 20,
})
watch(
() => person.name,
(newName) => {
console.log(newName)
},
)
</script>
監聽多個響應式數據
如果需要監聽多個響應式數據,可以在監聽的時候用陣列將多個響應式數據傳入,並在 callback 中使用 newValues
來取得多個響應式數據的值。
<script setup>
import { watch, ref } from "vue";
const firstName = ref("John");
const lastName = ref("Doe");
watch([firstName, lastName], (newValues) => {
// 這裡的 newValues 是陣列,裡面包含所有監聽的響應式數據的值
console.log(newValues);
});
</script>
watchEffect
如果要監聽的響應式數據較多,可以使用 watchEffect 來監聽。
watchEffect 會在初始化時執行一次,並在依賴的響應式數據變化時重新執行。
<script setup>
import { watchEffect, ref } from "vue";
const firstName = ref("John");
const lastName = ref("Doe");
watchEffect(() => {
if (firstName.value === "John" && lastName.value === "Doe") {
console.log("John Doe");
}
});
</script>