vue3的基本使用
1、Vue3簡介
- 2020年9月18日,Vue.js發布3.0版本,代號:One Piece(海賊王)
- 耗時2年多、2600+次提交、30+個RFC、600+次PR、99位貢獻者
- github上的tags地址:https://github.com/vuejs/vue-next/releases/tag/v3.0.0
1.1、Vue3帶來了什么
1.性能的提升
Vue3與Vue 2相比,在包大小(使用 tree-shaking 時減輕多達 41%)、初始渲染(速度提高多達 55%)、更新(多達 133% 更快)和內存使用(最多減少 54%)。
-
打包大小減少41%
-
初次渲染快55%, 更新渲染快133%
-
內存減少54%
......
2.源碼的升級
-
使用Proxy代替defineProperty實現響應式
-
重寫虛擬DOM的實現和Tree-Shaking
......
3.擁抱TypeScript
- Vue3可以更好的支持TypeScript
4.新的特性
-
Composition API(組合API)
- setup配置
- ref與reactive
- watch與watchEffect
- provide與inject
- ......
-
新的內置組件
- Fragment
- Teleport
- Suspense
-
其他改變
- 新的生命周期鉤子
- data 選項應始終被聲明為一個函數
- 移除keyCode支持作為 v-on 的修飾符
- ......
具體可查看:https://github.com/vuejs/core/releases?q=3.0.0&expanded=true
2、創建vue3工程
2.1、使用 vue-cli 創建工程
從 vue-cli 的 v4.5.0 版本開始,vue-cli 提供了內置選項,可在創建新項目時選擇 Vue 3。所以通過 vue-cli 創建 vue3項目需要保證 vue-cli 版本 >= v4.5.0。
## 查看@vue/cli版本,確保@vue/cli版本在4.5.0以上 vue --version ## 當版本過低時,安裝或者升級你的@vue/cli npm install -g @vue/cli
在更新 vue-cli 時,如果遇到以下錯誤:

則可能是 node.js 版本太低的原因,先更新 node.js 版本,比如升級至 v12.11.0 版本,然后卸載 vue-cli ,再重新安裝即可。
創建 vue3 項目,命令如下,當遇到選擇 vue3 還是 vue2 時,選擇 vue3 即可。
## 創建 vue create vue3_test ## 啟動項目 cd vue3_test npm run serve
可以看到運行結果如下,跟 vue2 沒有什么差別:

2.2、使用 vite 創建工程
- 什么是vite?—— 新一代前端構建工具。
- 優勢如下:
- 開發環境中,無需打包操作,可快速的冷啟動。
- 輕量快速的熱重載(HMR)。
- 真正的按需編譯,不再等待整個應用編譯完成。
- 傳統構建 與 vite構建對比圖

創建工程命令如下:
## 創建工程 npm init vite-app vue3_test_vite ## 進入工程目錄 cd vue3_test_vite ## 安裝依賴 npm install ## 運行 npm run dev
運行效果如下:

3、組合式API(Composition API)
3.1、composition和options的對比
options API 開發出來的vue應用如左圖所示,它的特點是理解容易,因為各個選項都有固定的書寫位置,比如響應式數據就寫到data選擇中,操作方法就寫到methods配置項中等,應用大了之后,相信大家都遇到過來回上下找代碼的困境
composition API開發的vue應用如右圖所示,它的特點是特定功能相關的所有東西都放到一起維護,比如功能A相關的響應式數據,操作數據的方法等放到一起,這樣不管應用多大,都可以快讀定位到某個功能的所有相關代碼,維護方便,設置如果功能復雜,代碼量大,我們還可以進行邏輯拆分處理


- 特別注意:
- 選項式api和組合式api倆種風格是并存的關系 并不是非此即彼
- 需要大量的邏輯組合的場景,可以使用compition API進行增強
4、組合式api之setup 組件選項
setup() 函數是vue3中專門新增的方法,可以理解為Composition Api的入口。
setup 函數的兩種返回值:
- 若返回一個對象,則對象中的屬性、方法, 在模板中均可以直接使用。(重點關注!)
- 若返回一個渲染函數:則可以自定義渲染內容。(了解)
使用示例:
<template> <h1>一個人的信息</h1> <h2>姓名:{{name}}</h2> <h2>年齡:{{age}}</h2> <h3>工作種類:{{job.name}}</h3> <h3>工作薪水:{{job.salary}}</h3> <button @click="changeInfo">修改人的信息</button> </template> <script> import {ref} from 'vue' export default { name: "App", setup(props) { //數據 let sex = "男" // 直接賦值的變量并不具備響應式功能 let name = ref("張三"); //需通過ref定義響應式數據 let age = ref(18); let job = ref({ name: "前端工程師", salary: "30K", }); //方法 function changeInfo() { console.log(sex, name, age) sex = "女" // 修改sex的值頁面無法得到實時更新,sex不是響應式的 name.value = '李四' age.value = 48 console.log(job.value); job.value.name = 'UI設計師' job.value.salary = '60K' } //返回一個對象(常用) return { name, age, job, changeInfo, }; }, }; </script>
效果如下:

注意:
- setup() 雖然可以和 vue2 中的一些配置(如data、methods等)共存,但盡量不要與Vue2.x配置一起使用。當 setup() 和 Vue2 中的配置(data、methos、computed...)同時存在時,在vue2 中的配置項可以訪問到setup中的屬性、方法。但在setup中不能訪問到Vue2的配置(data、methos、computed...)。并且如果兩者之間有同名的屬性值,則頁面中優先使用 setup 中的屬性值。
- setup不能是一個async函數,因為返回值不再是return的對象,而是promise,模板看不到return對象中的屬性。(后期也可以返回一個Promise實例,但需要Suspense和異步組件的配合)
setup 函數是一個新的組件選項,作為組件中組合式API 的起點(入口)。該函數有以下特性:
- setup() 函數是在 props 解析之后,beforeCreate() 執行之前執行的,并且 setup函數只會在組件初始化的時候執行一次。
setup的調用發生在data屬性、computed屬性、methods等等屬性被解析之前,所以它們無法在setup中被獲取。- 在 setup() 中你應該避免使用
this,因為它不會找到組件實例,在 setup() 中 this 指向的是 undefined。
4.1、setup() 函數的參數
setup() 函數有兩個參數:props 和 context。
- props:值為對象,包含:組件外部傳遞過來,且組件內部聲明接收了的屬性。
- context:上下文對象。context 是一個普通的 JavaScript 對象,它具有以下三個屬性:
- attrs:值為對象,包含了組件外部傳遞過來但沒有在 props 配置中聲明的屬性,相當于 vue2 中的
this.$attrs。 - slots:組件收到的插槽內容,相當于vue2 中的
this.$slots。 - emit:用于觸發自定義事件的函數,相當于vue2 中的
this.$emit。
示例:
子組件 Demo 定義如下:
<template> <button @click="test">測試觸發一下Demo組件的Hello事件</button> </template> <script> export default { name: 'Demo', props:['msg1'], emits:['hello'], setup(props,context){ console.log('props:',props) console.log('context:',context) console.log('context.attrs:',context.attrs) //相當與Vue2中的$attrs console.log('context.emit:',context.emit) //觸發自定義事件的。 console.log('context.slots:',context.slots) //插槽 //方法 function test(){ context.emit('hello',666) } //返回一個對象(常用) return { test } } } </script>
父組件如下:
<template> <Demo @hello="showHelloMsg" msg1="信息1" msg2="信息2" > <template v-slot:slot01> <span>插槽1</span> </template> <template v-slot:slot02> <span>插槽2</span> </template> </Demo> </template> <script> import Demo from "./components/Demo"; export default { name: "App", components: { Demo }, setup() { function showHelloMsg(value) { alert(`你好啊,你觸發了hello事件,我收到的參數是:${value}!`); } return { showHelloMsg, }; }, }; </script>
執行完成后,可以看到子組件的輸出如下:

可以看到,子組件的 setup() 函數中,輸出的參數 props 只包含了在子組件中已通過 props 配置項接收了的屬性值,而 context.attrs 只包含了未通過 props 配置接收的屬性值;context.emit 相當于 this.$emit,可用于觸發給子組件綁定了的自定義事件;context.slots 可獲取插槽內容。
5、組合式api之ref() 函數(返回 RefImpl 對象)
ref() 函數接受一個簡單類型或者復雜類型的傳入,并返回一個響應式且可變的 ref 實例對象(reference對象,簡稱ref對象)。當 ref 接收對象(或數組)類型數據時,實際上它的內部會自動通過 reactive 來監聽這些對象,實際上相當于使用了 reactive() 來監聽這些對象。
使用步驟:
- 從vue框架中導入ref函數
- 在setup函數中調用ref函數并傳入數據(簡單類型或者復雜類型),把ref函數的返回值以對象的形式返回
- 注意:在setup函數中使用ref結果,需要通過.value 訪問,模板中使用不需要加.value
示例如下:
<template> <div>{{ money }}</div> </template> <script> import { ref } from 'vue' export default { setup() { let money = ref(100) let obj = ref({ name: 'wen' }) console.log(11, money) console.log(22, obj) return { money } } } </script>
輸出如下:

ref() 函數接收的數據可以是基本類型和對象類型。基本類型數據的響應式依然是靠Object.defineProperty()的get與set完成的;而對象類型的數據在內部通過Vue3.0中的一個新函數 reactive函數來實現響應式。
使用 ref() 函數時,修改返回的響應式對象需要通過 value 屬性值進行修改,如:xxx.value = 'xxx'。但是在頁面模板中讀取數據時不需要 value,直接使用即可,如:<div>{{xxx}}</div>。
6、組合式api之reactive() 函數(返回 proxy 對象)
reactive 函數接收一個普通的對象傳入,把對象數據轉化為響應式對象并返回。
使用步驟:
- 從vue框架中導入reactive函數
- 在setup函數中調用reactive函數并將對象數據傳入,把reactive函數調用完畢之后的返回值以對象的形式返回
示例:
<template> <div>{{ state.name }}</div> <div>{{ state.age }}</div> <button @click="state.name = 'pink'">改值</button> <button @click="changeInfo">修改人的信息</button> </template> <script> import { reactive } from 'vue' export default { setup () { const state = reactive({ name: 'cp', age: 18 })
console.log(11, state)
function changeInfo(){ state.name = '李四' state.age = 48 } return { state, changeInfo } } } </script>
輸出如下:

reactive 不能接收基本類型作為參數。reactive 返回一個代理對象(Proxy的實例對象,簡稱proxy對象),reactive定義的響應式數據是“深層次的”,內部基于 ES6 的 Proxy 實現,通過代理對象操作源對象內部數據進行操作。
6.1、reactive 和 ref 的對比
- 從定義數據角度對比:
- ref用來定義:基本類型數據、對象類型。(ref 定義對象(或數組)類型數據時,它的內部會自動通過
reactive轉為代理對象。) - reactive用來定義:對象(或數組)類型數據。
- ref用來定義:基本類型數據、對象類型。(ref 定義對象(或數組)類型數據時,它的內部會自動通過
- 從原理角度對比:
- ref通過
Object.defineProperty()的get與set來實現響應式(數據劫持)。 - reactive通過使用Proxy來實現響應式(數據劫持), 并通過Reflect操作源對象內部的數據。
- ref通過
- 從使用角度對比:
- ref定義的數據:操作數據需要
.value,讀取數據時模板中直接讀取不需要.value。 - reactive定義的數據:操作數據與讀取數據:均不需要
.value。
- ref定義的數據:操作數據需要
7、組合式api之 computed() 函數
computed() 函數與Vue2.x中 computed 配置項功能基本一致。computed() 函數也可以像 computed 配置項一樣設置它的 setter。
使用示例:
<template> <span>全名:{{person.fullName}}</span> 全名:<input type="text" v-model="person.fullName"> </template> <script> import {reactive,computed} from 'vue' export default { name: 'Demo', setup(){ //數據 let person = reactive({ firstName:'張', lastName:'三' }) //計算屬性——簡寫(沒有考慮計算屬性被修改的情況) /* person.fullName = computed(()=>{ return person.firstName + '-' + person.lastName }) */ //計算屬性——完整寫法(考慮讀和寫) person.fullName = computed({ get(){ return person.firstName + '-' + person.lastName }, set(value){ const nameArr = value.split('-') person.firstName = nameArr[0] person.lastName = nameArr[1] } })
return { person } } } </script>
8、組合式api之 watch() 函數
watch() 函數與Vue2.x中 watch 配置項功能基本一致。
使用方法:首先需在 vue 中引入 watch,然后在 setup 中直接通過 watch() 函數監聽響應式數據。
語法如下:
// 第一個參數是監視的屬性名,可以監聽單個屬性,也可以一次性監聽多個屬性,此時該參數為數組。第二個參數為監視回調。第三個參數可選,可配置監視的一些配置,比如immediate、deep,類似于vue2的使用 watch(監視的屬性名, ()=> { ... }, {}),
8.1、watch() 監聽 ref 實例對象
當 watch 監聽 ref 實例對象 refImpl 時,實際上監聽的是實例對象的 value 屬性值。
使用示例如下:
setup(){ //數據 let sum = ref(0) let msg = ref('你好啊')//監視ref所定義的一個響應式數據 /* watch(sum,(newValue,oldValue)=>{ console.log('sum變了',newValue,oldValue) },{immediate:true}) */ //可一次性監視ref所定義的多個響應式數據。當然,也可以直接調用多個watch函數也行 watch([sum,msg],(newValue,oldValue)=>{ console.log('sum或msg變了',newValue,oldValue) },{immediate:true}) //返回一個對象(常用) return { sum, msg } }
當 ref() 函數參數為對象時,此時該實例對象的 value 值為一個 proxy 代理對象,所以如果此時監聽該實例對象時,實際上只有當整個實例對象發生改變時監聽回調函數才會觸發。如下:
<template> <h2>年齡:{{person.age}}</h2> <!-- 此時點擊按鈕并不會觸發監聽的回調函數 --> <button @click="person.age++">修改person.age的值</button> </template> <script> import {reactive, ref, watch} from 'vue' export default { name: 'Demo', props:['msg1'], emits:['hello'], setup(props,context){ let person = ref({ name: 'aa', age: 12 }) watch(person, (newValue,oldValue)=>{ console.log('person發生了改變', newValue, oldValue) }) return { person } } } </script>
此時點擊按鈕修改 person.age 的值并不會觸發監聽的回調函數,只有當給 person 重新賦值時才會觸發監聽的回調函數。
如果我們想要監聽 person 下的屬性值的改變時,此時可以將 watch 函數的參數改為 person.value,即監聽 proxy 代理對象,或者在 watch 函數的第三個參數添加 deep 參數。如下:
<template> <h2>年齡:{{person.age}}</h2> <!-- 此時點擊按鈕并不會觸發監聽的回調函數 --> <button @click="person.age++">修改person.age的值</button> </template> <script> import {reactive, ref, watch} from 'vue' export default { name: 'Demo', props:['msg1'], emits:['hello'], setup(props,context){ let person = ref({ name: 'aa', age: 12 }) //直接監聽 value 值 watch(person.value, (newValue,oldValue)=>{ console.log('person發生了改變', newValue, oldValue) }) // 添加deep配置 watch(person, (newValue,oldValue)=>{ console.log('person發生了改變', newValue, oldValue) },{ deep: true }) return { person } } } </script>
8.2、watch 監聽 reactive 響應式對象
通過 watch() 函數可以監聽 reactive() 返回的 proxy 響應式對象。
需要注意,如果監聽的是一個對象的話,監聽的回調函數無法正確獲取到 oldvalue 的值,輸出 oldvalue 會得到跟 newvalue 相同的值。
- 如果監聽的是 reactive 返回的整個對象的話,此時會默認開啟深度監聽,并且無法關閉深度監聽,另外還有點小問題,就是此時無法正確獲取 oldvalue 的值,輸出 oldvalue 會得到跟 newvalue 相同的值。
- 需要注意的是,如果監聽的是 ref() 函數以對象作為參數所返回的響應式數據,也同樣有上述問題。
- 如果想要監聽 reactive 返回的對象的某個屬性值的話,此時 watch() 函數的第一個參數應該是一個函數,并且返回值為需要監聽的對象的屬性。但此時并不會自動開啟深度監聽,也就是說,假如我們監聽的這個屬性值是對象的話,修改這個對象的屬性是不會觸發監聽回調函數的。
- 如果監聽的這個 reactive 返回的對象的屬性是字符串的話,則此時能正確獲取到 oldvalue 的值
- 如果監聽的這個 reactive 返回的對象的屬性是對象的話,此時就無法正確獲取到 oldvalue 的值
setup(){ //數據 let sum = ref(0) let msg = ref('你好啊') let person = reactive({ name:'張三', age:18, job:{ j1:{ salary:20 } } }) /* 監視reactive所定義的一個響應式數據的全部屬性 1.注意:此處無法正確的獲取oldValue,輸出的oldvalue的值總是等于newvalue的值 2.注意:此時會強制開啟深度監視(并且無法通過deep配置關閉深度監聽) */ watch(person,(newValue,oldValue)=>{ console.log('person變化了',newValue,oldValue) },{deep:false}) //此處的deep配置無效 //可以直接監視reactive所定義的一個響應式數據中的某個屬性。此時可以正確獲取oldvalue值的情況 watch(()=>person.name,(newValue,oldValue)=>{ console.log('person的name變化了',newValue,oldValue) }) //一次性監視多個reactive返回的響應式對象的屬性 watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{ console.log('person的name或age變化了',newValue,oldValue) }) //特殊情況,監聽的屬性值是一個對象,此時也無法獲取oldvalue的值 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job變化了',newValue,oldValue) },{deep:true}) //此處由于監視的是reactive素定義的對象中的某個屬性,所以deep配置有效 */ //返回一個對象(常用) return { sum, msg, person } }
9、組合式api之 watchEffect() 函數
watchEffect() 函數不用指明監視哪個屬性,監視的回調中用到哪寫屬性,那就會監視哪些屬性,當這些屬性值發生改變時,回調函數就會執行。
watchEffect有點像computed,但computed注重的計算出來的值(回調函數的返回值),所以必須要寫返回值,而watchEffect更注重的是過程(回調函數的函數體),所以不用寫返回值。
示例如下:
export default { name: 'Demo', setup(){ //數據 let sum = ref(0) let msg = ref('你好啊') let person = reactive({ name:'張三', age:18, job:{ j1:{ salary:20 } } }) watchEffect(()=>{ const x1 = sum.value const x2 = person.job.j1.salary console.log('watchEffect所指定的回調執行了') }) //返回一個對象(常用) return { sum, msg, person } } }
則只要 sum 或者 person.job.j1.salary 的值發生改變,watchEffect() 函數就會執行。
10、vue3的生命周期
Vue3 和 Vue2 中的生命周期鉤子的工作方式類似,名稱也差不多,只是有兩個鉤子函數的名稱改了而已,實際上他們的觸發時機都是一樣的。
vue3 的生命周期如下:
- beforeCreate
- created
- beforeMount
- mounted
- beforeupdate
- updated
-
beforeUnmount(相當于vue2中的beforeDestroy)
-
unmounted(相當于vue2中的destroyed)

使用示例:
export default { name: 'Demo', setup(){ console.log('---setup---') //數據 let sum = ref(0) //返回一個對象(常用) return {sum} }, //通過配置項的形式使用生命周期鉤子 beforeCreate() { console.log('---beforeCreate---') }, created() { console.log('---created---') }, beforeMount() { console.log('---beforeMount---') }, mounted() { console.log('---mounted---') }, beforeUpdate(){ console.log('---beforeUpdate---') }, updated() { console.log('---updated---') }, beforeUnmount() { console.log('---beforeUnmount---') }, unmounted() { console.log('---unmounted---') } }
10.1、組合式api形式的生命周期鉤子
Vue3.0 中提供了 Composition API 形式的生命周期鉤子,你可以通過在生命周期鉤子前面加上 “on” 來訪問組件的生命周期鉤子。
beforeCreate===>setup()created=====>setup()beforeMount===>onBeforeMountmounted=====>onMountedbeforeUpdate===>onBeforeUpdateupdated====>onUpdatedbeforeUnmount==>onBeforeUnmountunmounted=====>onUnmounted
因為 setup 是圍繞 beforeCreate 和 created 生命周期鉤子運行的,所以不需要顯式地定義這兩個鉤子。換句話說,在這些鉤子中編寫的任何代碼都應該直接在 setup 函數中編寫。
使用示例:
import {ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue'
export default {
name: 'Demo',
setup(){
console.log('---setup---')
//數據
let sum = ref(0)
//通過組合式API的形式去使用生命周期鉤子
onBeforeMount(()=>{
console.log('---onBeforeMount---')
})
onMounted(()=>{
console.log('---onMounted---')
})
onBeforeUpdate(()=>{
console.log('---onBeforeUpdate---')
})
onUpdated(()=>{
console.log('---onUpdated---')
})
onBeforeUnmount(()=>{
console.log('---onBeforeUnmount---')
})
onUnmounted(()=>{
console.log('---onUnmounted---')
})
//返回一個對象(常用)
return {sum}
}
}
實際上,組合式 api 形式的生命周期鉤子和配置項形式的鉤子可以同時存在,當同時存在時,組合式 api 形式的執行要先于對應的相同的配置項的鉤子。但我們不建議同時使用組合式 api 和配置項的鉤子,沒什么必要。
11、Composition API 的優勢
使用傳統OptionsAPI中,新增或者修改一個需求,就需要分別在data,methods,computed里修改 。使用 Composition API 我們可以更加優雅的組織我們的代碼,函數。讓相關功能的代碼更加有序的組織在一起。

浙公網安備 33010602011771號