1. 前言
vue3 更好的支持了 Typescript
,新增了 CompositionAPI
,而且在性能方面有很大提升
- 打包大小減少 41%
- 初次渲染快 55%,更新快 133%
- 內存使用減少 54%
這篇文章主要來學習以下 vue3 的以下新特性:ref()
、reactive()
、toRef()
、toRefs()
2. 版本要求
對於 Vue3,node 版本 10+、Vue Cli v4.5+的版本可用。已經安裝過@vue/cli
需更新至最新版本
npm install -g @vue/cli
or
yarn global add @vue/cli
3. 配置 Vue3 開發環境
vue create [project-name]
?Please pick a preset:
Manually select feature(手動選擇一些特性)
然后,是一系列可插拔的支持,囊括各種功能,充分體現了漸進式的特點
?Check the feature needed for your project:
Choose vue version
Babel
Typescript
Linter/Formatter(代碼格式檢查工具)
?Choose a version of vue.js that you want to start the project with:
3.X(Preview)
? Use class-style component snytax?
(類類型的組件)
No
?Use Babel alongSide Typescript?
No
?Pick a linter / formatter config:
Eslint with error prevention only
? Pick additional lint features:
Lint on save
? Where do you prefer placing config for Babel,Eslint ,etc.?
In dedicated config files
? Save this as a preset for future projects?
No(根據自己的需要選擇)
OR 使用 UI 界面:
vue ui
進入圖文界面進行選擇配置
4. vue2 與 vue3 的響應式實現
我們知道,vue2 中的 data 返回的是一個響應式對象,其原理是通過 Object.defineProperty()
實現的,但是會有一些弊端,比如它對於對象上新增的屬性無能為力;對於數組則需要攔截它的原型方法來實現響應式。
那么,vue3 踏着五彩祥雲就來了。vue3 是使用 ES6 中的新特性 Proxy 來實現響應式。先來對 Proxy
進行一下預熱,
Proxy
對象用於定義基本操作的自定義行為(如:屬性查找、賦值、枚舉、函數調用等)。
Proxy
可以理解成,在目標對象之前架設一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。
更多內容可以參考 ES6 Proxy
5. ref() 和 reactive()
ref()
函數接收一個基本數據類型的參數同時返回一個基於該值的響應性對象,該對象內部有且僅有一個屬性 value,該對象中的值一旦被改變和訪問就會被跟蹤到,通過修改refData.value
的值,可以觸發模版的重新的渲染,顯示最新的值。reactive
則是修改state.reactiveField
的值。
reactive()
函數接收一個復雜數據類型的數據(對象或數組)作為參數,並返回一個響應式代理對象。(響應式數據即當數據發生變化時 UI 也會自動更新)
<template>
<div>
<h3>{{ temp }}</h3>
<p>{{ user.name }}</p>
<p>{{ user.age }}</p>
<button @click="increase">click me!</button>
</div>
</template>
import { ref, reactive } from 'vue'
export default {
setup() {
const temp = ref(0)
temp.value = 'hello'
const user = reactive({ name: 'lemon', age: 20 })
console.log(temp)
console.log(temp.value) // hello
console.log(user) // Proxy {name:'lemon',age:20}
const increase = () => {
user.age++
}
return { temp, user, increase }
}
}
reactive()
函數可以代理一個復雜數據類型比如:對象、數組,但不能代理基本類型值,例如字符串、數字、boolean 等,這是 js 語言的限制,因此我們需要使用 ref()
函數來間接對基本類型值進行處理。ref
的本質還是reactive
系統會自動根據ref()
函數的入參將其轉換成ref(x)
即reactive({value:x})
綜上,
ref(user.name)
就相當於ref('lemon')
也相當於reactive({value:'lemon'})
注意:
- 在 vue 模板中使用
ref
的值不需要通過value
屬性獲取(vue 會自動給 ref 的值加上.value) - 在 js 中使用
ref
的值要通過.value
獲取
6. toRef() 和 toRefs()
toRef
是將個對象 A 中的某個屬性 x 轉換為響應式數據,其接收兩個參數,第一個參數為對象 A,第二個參數為對象中的某個屬性名 x。
import { toRef } from 'vue'
export default {
setup() {
const user = { name: 'lemon', age: 3 }
const userRef = toRef(user, 'age')
return { userRef }
}
}
到這里你應該會有一個疑問,toRef()
和ref()
都是創建響應式數據的函數,它們之間有什么不同呢?我們來測試一下。
<template>
<p>
響應式數值ref0:
<button @click="add0">add {{ state0 }}</button>
</p>
<p>
響應式對象ref1:
<button @click="add1">Add {{ state1 }}</button>
</p>
<p>
響應式對象toRef2:
<button @click="add2">Add {{ state2 }}</button>
</p>
</template>
<script lang="ts">
import { ref, toRef } from "vue";
export default {
setup() {
const temp = { count: 1 };
const temp0 = 1;
const state0 = ref(temp0);
const state1 = ref(temp.count);
const state2 = toRef(temp, "count");
const add0 = () => {
state0.value++;
console.log("原始值:", temp0); //原始值:1
console.log("響應式數據對象ref:", state0.value); //響應式數值ref:2
};
const add1 = () => {
state1.value++;
console.log("原始值:", temp); //原始值:1
console.log("響應式數據對象ref:", state1.value); //響應式對象ref:2
};
const add2 = () => {
state2.value++;
console.log("原始值:", temp); // 原始值:2
console.log("響應式數據對象toRef:", state2.value); //響應式對象toRef:2
};
return { state0, state1, state2, add0, add1, add2 };
},
};
</script>
綜上,ref()和 refs()有以下區別:
- 參數不同:
ref()
接收一個 js 基本數據類型的參數;toRef()
接收兩個參數,第一個為對象,第二個為對象中的某個屬性; - 原理不同:
ref()
是對原數據的一個深拷貝,當其值改變時不會影響到原始值;toRef()
是對原數據的一個引用,當值改變時會影響到原始值; - 響應性不同:
ref()
創建的數據會觸發 vue 模版更新;toRef()
創建的響應式數據並不會觸發 vue 模版更新,所以toRef()
的本質是引用,與原始數據有關聯。
toRefs()
接收一個對象作為參數,並遍歷對象身上的所有屬性,然后逐個調用toRef()
執行。以此,將響應式對象轉化為普通對象,便於在模版中可以直接使用屬性。
當我們希望對象的多個屬性都變成響應式數據,並且要求響應式數據和原始數據相關聯,並且更新響應式數據時不更新界面,這時候toRefs()
就派上用場了,它用於批量設置多個響應式數據。
那么,到這里又有疑問了,通過上面的學習我們知道使用 reactive()創建的數據已經具有響應式了,為什么還要再 toRefs()呢?
往下看,
setup() {
// 創建一個響應式對象state
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state) // 將響應式的對象變為普通對象結構
// The ref and the original property is "linked"
state.foo++
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
return { temp, userRefs, ...stateAsRefs, add };
}
對於以上代碼,toRefs()將響應式的對象 state 變為普通對象 stateAsRefs 后,return 時使用 ES6 的擴展運算符,在模版中可以直接使用其內部屬性,且仍具有響應性( 對響應式對象 state 使用擴展運算符后,其內部屬性就失去了響應性 )
官方案例:
function useFeatureX() {
const state = reactive({
foo: 1,
bar: 2
})
// logic operating on state
// convert to refs when returning(返回時轉為refs)
return toRefs(state)
}
export default {
setup() {
// can destructure without losing reactivity(可以解構而不失去其響應性)
const { foo, bar } = useFeatureX()
return {
foo,
bar
}
}
}
對響應式對象進行 toRefs 后,可以對其進行解構方便 vue 模版使用,但是不會使其失去響應性。