對比ref
和reactive
Vue2回顧
首先回顧一下在Vue2中我們是如何創建一個響應式數據 (reactive data)的:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<h1>{{ title }}</h1>
<button @click="handleClick">?</button>
</template>
<script lang="ts">
//在lang="ts"的環境下,要在vue3中使用vue2的API,需要使用 defineComponent 包裹一下整個對象,來確保this的指向不出問題
import { defineComponent } from 'vue';
export default defineComponent({
name: "App",
data() {
return {
title: "你好,Vue3!",
};
},
methods: {
handleClick() {
this.title = "海賊王來了";
},
},
});
</script>
Vue3新特性
ref
的使用
而在Vue3中,我們可以用Composition API: ref
來改寫上述代碼:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<h1>{{ title }}</h1>
<button @click="handleClick">?</button>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
name: "App",
setup() {
const title = ref("你好, Vue3!");
const handleClick = () => {
title.value = "海賊王來了";
};
return { title, handleClick };
},
});
</script>
ref
的作用就是將一個原始數據類型(primitive data type)轉換成一個帶有響應式特性(reactivity)的數據類型,原始數據類型共有7個,分別是:
- String
- Number
- BigInt
- Boolean
- Symbol
- Null
- Undefined
相比于Vue2,用ref
的好處就是傳值時可以不用再寫this
那如果我想讓一個對象(Object)也具有響應性(reactive) 這一特點呢?
reactive
的使用
Vue3提供了一個方法:reactive
(等價于Vue2中的Vue.observable()
)來賦予對象(Object) 響應式的特性,那么我們可以將上述代碼用對象的形式改寫為:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<h1>{{ data.title }}</h1>
<button @click="data.handleClick">?</button>
</template>
<script lang="ts">
import { defineComponent, reactive } from "vue";
export default defineComponent({
name: "App",
setup() {
const data = reactive({
title: "你好, Vue3",
handleClick: () => {
data.title = "海賊王來了";
},
});
return { data };
},
});
</script>
你可能會覺得data.xxx
的寫法太麻煩,那么我們可以使用es6新語法擴展運算符來簡化一下:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<h1>{{ title }}</h1>
<button @click="handleClick">?</button>
</template>
<script lang="ts">
import { defineComponent, reactive } from "vue";
export default defineComponent({
name: "App",
setup() {
const data = reactive({
title: "你好, Vue3",
handleClick: () => {
data.title = "海賊王來了";
},
});
return { ...data };
},
});
</script>
Bug出現
不出意外,你會發現這個簡化后的代碼竟然無效,不管怎么點按鈕,頁面并沒有發生變化!事實上,這樣寫沒有效果的原因就在于一個響應型對象(reactive object) 一旦被銷毀或展開(如上面代碼那樣),其響應式特性(reactivity)就會丟失。通過類型檢查我們可以知道,雖然 data.title
的值確實發生了變化,但data.title
的類型只是一個普通的字符串(String) ,并不具有響應式特性(reactivity),故而頁面也沒有隨著其值的變化而重新渲染。
toRefs
的作用
為了解決上述問題,Vue3又提供了一個新的API:toRefs
,它可以將一個響應型對象(reactive object) 轉化為普通對象(plain object),同時又把該對象中的每一個屬性轉化成對應的響應式屬性(ref)。說白了就是放棄該對象(Object)本身的響應式特性(reactivity),轉而給對象里的屬性賦予響應式特性(reactivity)。故而我們可以將代碼修復成下面這樣:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<h1>{{ title }}</h1>
<button @click="handleClick">?</button>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from "vue";
export default defineComponent({
name: "App",
setup() {
const data = reactive({
title: "你好, Vue3",
handleClick: () => {
data.title = "海賊王來了";
},
});
const dataAsRefs = toRefs(data);
/*
Type of dataAsRefs:
{
title: Ref<string>,
handleClick: Ref<() => void>
}
*/
return { ...dataAsRefs };
},
});
</script>
總結
ref
和 reactive
一個針對原始數據類型,而另一個用于對象,這兩個API都是為了給JavaScript普通的數據類型賦予響應式特性(reactivity)。根據Vue3官方文檔,這兩者的主要區別在于每個人寫JavaScript時的風格不同,有人喜歡用原始數據類型(primitives),把變量單獨拎出來寫;而有人喜歡用對象(Object),把變量當作對象里的屬性,都寫在一個對象里頭,比如:
// style 1: separate variables
let x = 0
let y = 0
function updatePosition(e) {
x = e.pageX
y = e.pageY
}
// --- compared to ---
// style 2: single object
const pos = {
x: 0,
y: 0
}
function updatePosition(e) {
pos.x = e.pageX
pos.y = e.pageY
}
(完)
參考資料
[1]:https://vue-composition-api-rfc.netlify.app/#ref-vs-reactive
[2]:https://www.danvega.dev/blog/2020/02/12/vue3-ref-vs-reactive/
[3]:https://coding.imooc.com/learn/list/449.html