Vue3/Vue2 響應式比較
Vue2 Options API 注意事項
無法檢測 property 的添加或移除
Vue 無法檢測 property 的添加或移除。由於 Vue 會在初始化實例時對 property 執行 getter/setter 轉化,所以 property 必須在 data 對象上存在才能讓 Vue 將它轉換為響應式的。例如:
var vm = new Vue({
data:{
a:1
}
})
// `vm.a` 是響應式的
vm.b = 2
// `vm.b 是非響應式的
對於已經創建的實例,Vue 不允許動態添加根級別的響應式 property。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套對象添加響應式 property。
mounted () { // ——鈎子函數,實例掛載之後
this.$set(this.student,"age", 24)
}
已有對象賦值多個新 property,比如使用 Object.assign()
有時你可能需要為已有對象賦值多個新 property,比如使用 Object.assign() 或 _.extend()。但是,這樣添加到對象上的新 property 不會觸發更新。在這種情況下,你應該用原對象與要混合進去的對象的 property 一起創建一個新的對象。
// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
Vue3 Composition API
reactive type
具有響應式的資料儲存方式,reactive()內只能放入物件或陣列,如果放入這兩個以外的資料形式則會報錯。 在reactive內的的資料屬性一樣具有響應式的特性每個屬性都可以看做是一個ref,在呼叫reactive會進行"解包",所以不用像ref要加上.value。
<script setup>
import { reactive } from 'vue'
const state = reactive<{
// 可選:ts設置型別
localStartVal: number
printVal: number | null
displayValue: string
paused: boolean
localDuration: number | null
startTime: number | null
timestamp: number | null
rAF: any
count: number | null
}>({
localStartVal: props.startVal,
displayValue: formatNumber(props.startVal),
printVal: null,
paused: false,
localDuration: props.duration,
startTime: null,
timestamp: null,
remaining: null,
count: 0
})
function increment() {
state.count++
}
</script>
<template>
<button @click="increment">
{{ state.count }}
</button>
</template>
reactive 深層響應性
在 Vue3 中,狀態都是默認深層響應式的。這意味著即使在更改深層次的對象或數組,你的改動也能被檢測到。
import { reactive } from 'vue'
const obj = reactive({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// 以下都會按照期望工作
obj.nested.count++
obj.arr.push('baz')
}
reactive() 的侷限性
reactive() API 有兩條限制:
- 僅對對象類型有效(對象、數組和 Map、Set 這樣的集合類型),而對 string、number 和 boolean 這樣的 原始類型 無效。
- 因為 Vue 的響應式系統是通過屬性訪問進行追蹤的,因此我們必須始終保持對該響應式對象的相同引用。這意味著我們不可以隨意地“替換”一個響應式對象,因為這將導致對初始引用的響應性連接丟失:
let state = reactive({ count: 0 })
// 上面的引用 ({ count: 0 }) 將不再被追蹤(響應性連接已丟失!)
state = reactive({ count: 1 })
同時這也意味著當我們將響應式對象的屬性賦值或解構至本地變量時,或是將該屬性傳入一個函數時,我們會失去響應性:
const state = reactive({
count: 0 ,
name:'Nick'
})
// n 是一個局部變量,同 state.count
// 失去響應性連接
let n = state.count
// 不影響原始的 state
n++
// count 也和 state.count 失去了響應性連接
let { count } = state
// 不會影響原始的 state
count++
// 該函數接收一個普通數字,並且
// 將無法跟蹤 state.count 的變化
callSomeFunction(state.count)
toRef
基於響應式對象上的一個屬性,創建一個對應的 ref。這樣創建的 ref 與其源屬性保持同步
// 承上面範例
const state = reactive({
count: 0 ,
name:'Nick'
})
// n 是一個局部變量,同 state.count
// 失去響應性連接
let n = state.count
const userName = toRef(state, 'name')
// toRef 將響應物件裡的屬性 ref 抽離出來,userName 可保持響應性
toRefs
將一個響應式對象轉換為一個普通對象,這個普通對象的每個屬性都是指向源對象相應屬性的 ref。每個單獨的 ref 都是使用 toRef() 創建的。
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state)
/*
stateAsRefs 的類型:{
foo: Ref<number>,
bar: Ref<number>
}
*/
// 這個 ref 和源屬性已經“鏈接上了”
state.foo++
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
當從組合式函數中返回響應式對象時,toRefs 相當有用。使用它,消費者組件可以解構/展開返回的對象而不會失去響應性:
function useFeatureX() {
const state = reactive({
foo: 1,
bar: 2
})
// ...基於狀態的操作邏輯
// 在返回時都轉為 ref
return toRefs(state)
}
// 可以解構而不會失去響應性
const { foo, bar } = useFeatureX()
toRefs 在調用時只會為源對象上可以枚舉的屬性創建 ref。如果要為可能還不存在的屬性創建 ref,請改用 toRef。
ref
具有響應式的資料儲存方式,ref()可以存入任何型別的資料。 但是讀取與寫入的時候需要.value。
import { ref } from 'vue'
// 得到的類型:Ref<string | number>
const year = ref<string | number>('2020')
year.value = 2020 // 成功!
如果你指定了一個泛型參數但沒有給出初始值,那麼最後得到的就將是一個包含 undefined 的聯合類型:
// 推導得到的類型:Ref<number | undefined>
const n = ref<number>()
computed()
coumputed所return的資料本身就是個ref
const data = ref({name:'Jack'})
// 推導得到的類型:ComputedRef<String>
const dataComputed = computed(() => {
return `我是${data.value.name}`
})
setTimeout(()=>{
data.value.name = 'Jay'
},2000)
console.log(dataComputed.value)// 我是Jay
// => TS Error: Property 'addition' does not exist on type 'string'
const result = dataComputed.value++
DOM 更新時機
當你更改響應式狀態後,DOM 會自動更新。然而,你得注意 DOM 的更新並不是同步的。相反,Vue 將緩衝它們直到更新週期的 “下個時機” 以確保無論你進行了多少次狀態更改,每個組件都只需要更新一次。
若要等待一個狀態改變後的 DOM 更新完成,你可以使用 nextTick() 這個全局 API:
import { nextTick } from 'vue'
function increment() {
state.count++
nextTick(() => {
// 訪問更新後的 DOM
})
}