1. Vue3簡介
- 2020年9月18日,
Vue.js
發(fā)布版3.0
版本崖咨,代號:One Piece
(n - 經(jīng)歷了:4800+次提交骤坐、40+個RFC绪杏、600+次PR、300+貢獻(xiàn)者
- 官方發(fā)版地址:Release v3.0.0 One Piece · vuejs/core
- 截止2023年10月纽绍,最新的公開版本為:
3.3.4
1.1. 【性能的提升】
打包大小減少
41%
蕾久。初次渲染快
55%
, 更新渲染快133%
。內(nèi)存減少
54%
拌夏。
1.2.【 源碼的升級】
使用
Proxy
代替defineProperty
實(shí)現(xiàn)響應(yīng)式僧著。重寫虛擬
DOM
的實(shí)現(xiàn)和Tree-Shaking
。
1.3. 【擁抱TypeScript】
-
Vue3
可以更好的支持TypeScript
障簿。
1.4. 【新的特性】
-
Composition API
(組合API
):setup
ref
與reactive
-
computed
與watch
......
-
新的內(nèi)置組件:
Fragment
Teleport
-
Suspense
......
-
其他改變:
新的生命周期鉤子
data
選項(xiàng)應(yīng)始終被聲明為一個函數(shù)-
移除
keyCode
支持作為v-on
的修飾符......
2. 創(chuàng)建Vue3工程
2.1. 【基于 vue-cli 創(chuàng)建】
點(diǎn)擊查看官方文檔
備注:目前
vue-cli
已處于維護(hù)模式盹愚,官方推薦基于Vite
創(chuàng)建項(xiàng)目。
## 查看@vue/cli版本站故,確保@vue/cli版本在4.5.0以上
vue --version
## 安裝或者升級你的@vue/cli
npm install -g @vue/cli
## 執(zhí)行創(chuàng)建命令
vue create vue_test
## 隨后選擇3.x
## Choose a version of Vue.js that you want to start the project with (Use arrow keys)
## > 3.x
## 2.x
## 啟動
cd vue_test
npm run serve
2.2. 【基于 vite 創(chuàng)建】(推薦)
vite
是新一代前端構(gòu)建工具皆怕,官網(wǎng)地址:https://vitejs.cn,vite
的優(yōu)勢如下:
- 輕量快速的熱重載(
HMR
)西篓,能實(shí)現(xiàn)極速的服務(wù)啟動愈腾。 - 對
TypeScript
、JSX
岂津、CSS
等支持開箱即用虱黄。 - 真正的按需編譯,不再等待整個應(yīng)用編譯完成寸爆。
-
webpack
構(gòu)建 與vite
構(gòu)建對比圖如下:
- 具體操作如下(點(diǎn)擊查看官方文檔)
## 1.創(chuàng)建命令
npm create vue@latest
## 2.具體配置
## 配置項(xiàng)目名稱
√ Project name: vue3_test
## 是否添加TypeScript支持
√ Add TypeScript? Yes
## 是否添加JSX支持
√ Add JSX Support? No
## 是否添加路由環(huán)境
√ Add Vue Router for Single Page Application development? No
## 是否添加pinia環(huán)境
√ Add Pinia for state management? No
## 是否添加單元測試
√ Add Vitest for Unit Testing? No
## 是否添加端到端測試方案
√ Add an End-to-End Testing Solution? ? No
## 是否添加ESLint語法檢查
√ Add ESLint for code quality? Yes
## 是否添加Prettiert代碼格式化
√ Add Prettier for code formatting? No
自己動手編寫一個App組件
<template>
<div class="app">
<h1>你好敖嘎场盐欺!</h1>
</div>
</template>
<script lang="ts">
export default {
name:'App' //組件名
}
</script>
<style>
.app {
background-color: #ddd;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
</style>
安裝官方推薦的vscode
插件:
總結(jié):
-
Vite
項(xiàng)目中,index.html
是項(xiàng)目的入口文件仅醇,在項(xiàng)目最外層冗美。 - 加載
index.html
后,Vite
解析<script type="module" src="xxx">
指向的JavaScript
析二。 -
Vue3
**中是通過 **createApp
函數(shù)創(chuàng)建一個應(yīng)用實(shí)例粉洼。
2.3. 【一個簡單的效果】
Vue3
向下兼容Vue2
語法,且Vue3
中的模板中可以沒有根標(biāo)簽
<template>
<div class="person">
<h2>姓名:{{name}}</h2>
<h2>年齡:{{age}}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">年齡+1</button>
<button @click="showTel">點(diǎn)我查看聯(lián)系方式</button>
</div>
</template>
<script lang="ts">
export default {
name:'App',
data() {
return {
name:'張三',
age:18,
tel:'13888888888'
}
},
methods:{
changeName(){
this.name = 'zhang-san'
},
changeAge(){
this.age += 1
},
showTel(){
alert(this.tel)
}
},
}
</script>
3. Vue3核心語法
3.1. 【OptionsAPI 與 CompositionAPI】
-
Vue2
的API
設(shè)計是Options
(配置)風(fēng)格的叶摄。 -
Vue3
的API
設(shè)計是Composition
(組合)風(fēng)格的属韧。
Options API 的弊端
Options
類型的 API
,數(shù)據(jù)蛤吓、方法宵喂、計算屬性等,是分散在:data
会傲、methods
锅棕、computed
中的,若想新增或者修改一個需求淌山,就需要分別修改:data
裸燎、methods
、computed
泼疑,不便于維護(hù)和復(fù)用德绿。
Composition API 的優(yōu)勢
可以用函數(shù)的方式,更加優(yōu)雅的組織代碼退渗,讓相關(guān)功能的代碼更加有序的組織在一起移稳。
說明:以上四張動圖原創(chuàng)作者:大帥老猿
3.2. 【拉開序幕的 setup】
setup 概述
setup
是Vue3
中一個新的配置項(xiàng),值是一個函數(shù)氓辣,它是 Composition API
“表演的舞臺”秒裕,組件中所用到的:數(shù)據(jù)、方法钞啸、計算屬性几蜻、監(jiān)視......等等,均配置在setup
中体斩。
特點(diǎn)如下:
-
setup
函數(shù)返回的對象中的內(nèi)容梭稚,可直接在模板中使用。 -
setup
中訪問this
是undefined
絮吵。 -
setup
函數(shù)會在beforeCreate
之前調(diào)用弧烤,它是“領(lǐng)先”所有鉤子執(zhí)行的。
<template>
<div class="person">
<h2>姓名:{{name}}</h2>
<h2>年齡:{{age}}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">年齡+1</button>
<button @click="showTel">點(diǎn)我查看聯(lián)系方式</button>
</div>
</template>
<script lang="ts">
export default {
name:'Person',
setup(){
// 數(shù)據(jù)蹬敲,原來寫在data中(注意:此時的name暇昂、age莺戒、tel數(shù)據(jù)都不是響應(yīng)式數(shù)據(jù))
let name = '張三'
let age = 18
let tel = '13888888888'
// 方法,原來寫在methods中
function changeName(){
name = 'zhang-san' //注意:此時這么修改name頁面是不變化的
console.log(name)
}
function changeAge(){
age += 1 //注意:此時這么修改age頁面是不變化的
console.log(age)
}
function showTel(){
alert(tel)
}
// 返回一個對象急波,對象中的內(nèi)容从铲,模板中可以直接使用
return {name,age,tel,changeName,changeAge,showTel}
}
}
</script>
setup 的返回值
- 若返回一個對象:則對象中的:屬性、方法等澄暮,在模板中均可以直接使用(重點(diǎn)關(guān)注)名段。
- 若返回一個函數(shù):則可以自定義渲染內(nèi)容,代碼如下:
setup(){
return ()=> '你好捌谩伸辟!'
}
setup 與 Options API 的關(guān)系
-
Vue2
的配置(data
、methos
......)中可以訪問到setup
中的屬性馍刮、方法信夫。 - 但在
setup
中不能訪問到Vue2
的配置(data
、methos
......)渠退。 - 如果與
Vue2
沖突忙迁,則setup
優(yōu)先。
setup 語法糖
setup
函數(shù)有一個語法糖碎乃,這個語法糖,可以讓我們把setup
獨(dú)立出去惠奸,代碼如下:
<template>
<div class="person">
<h2>姓名:{{name}}</h2>
<h2>年齡:{{age}}</h2>
<button @click="changName">修改名字</button>
<button @click="changAge">年齡+1</button>
<button @click="showTel">點(diǎn)我查看聯(lián)系方式</button>
</div>
</template>
<script lang="ts">
export default {
name:'Person',
}
</script>
<!-- 下面的寫法是setup語法糖 -->
<script setup lang="ts">
console.log(this) //undefined
// 數(shù)據(jù)(注意:此時的name梅誓、age、tel都不是響應(yīng)式數(shù)據(jù))
let name = '張三'
let age = 18
let tel = '13888888888'
// 方法
function changName(){
name = '李四'//注意:此時這么修改name頁面是不變化的
}
function changAge(){
console.log(age)
age += 1 //注意:此時這么修改age頁面是不變化的
}
function showTel(){
alert(tel)
}
</script>
擴(kuò)展:上述代碼佛南,還需要編寫一個不寫setup
的script
標(biāo)簽梗掰,去指定組件名字,比較麻煩嗅回,我們可以借助vite
中的插件簡化
- 第一步:
npm i vite-plugin-vue-setup-extend -D
- 第二步:
vite.config.ts
import { defineConfig } from 'vite'
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
export default defineConfig({
plugins: [ VueSetupExtend() ]
})
- 第三步:
<script setup lang="ts" name="Person">
3.3. 【ref 創(chuàng)建:基本類型的響應(yīng)式數(shù)據(jù)】
- 作用:定義響應(yīng)式變量及穗。
-
語法:
let xxx = ref(初始值)
。 -
返回值:一個
RefImpl
的實(shí)例對象绵载,簡稱ref對象
或ref
埂陆,ref
對象的value
屬性是響應(yīng)式的。 -
注意點(diǎn):
-
JS
中操作數(shù)據(jù)需要:xxx.value
娃豹,但模板中不需要.value
焚虱,直接使用即可。 - 對于
let name = ref('張三')
來說懂版,name
不是響應(yīng)式的鹃栽,name.value
是響應(yīng)式的。
-
<template>
<div class="person">
<h2>姓名:{{name}}</h2>
<h2>年齡:{{age}}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">年齡+1</button>
<button @click="showTel">點(diǎn)我查看聯(lián)系方式</button>
</div>
</template>
<script setup lang="ts" name="Person">
import {ref} from 'vue'
// name和age是一個RefImpl的實(shí)例對象躯畴,簡稱ref對象薇芝,它們的value屬性是響應(yīng)式的。
let name = ref('張三')
let age = ref(18)
// tel就是一個普通的字符串丰嘉,不是響應(yīng)式的
let tel = '13888888888'
function changeName(){
// JS中操作ref對象時候需要.value
name.value = '李四'
console.log(name.value)
// 注意:name不是響應(yīng)式的夯到,name.value是響應(yīng)式的,所以如下代碼并不會引起頁面的更新供嚎。
// name = ref('zhang-san')
}
function changeAge(){
// JS中操作ref對象時候需要.value
age.value += 1
console.log(age.value)
}
function showTel(){
alert(tel)
}
</script>
3.4. 【reactive 創(chuàng)建:對象類型的響應(yīng)式數(shù)據(jù)】
-
作用:定義一個響應(yīng)式對象(基本類型不要用它黄娘,要用
ref
,否則報錯) -
語法:
let 響應(yīng)式對象= reactive(源對象)
克滴。 -
返回值:一個
Proxy
的實(shí)例對象逼争,簡稱:響應(yīng)式對象。 -
注意點(diǎn):
reactive
定義的響應(yīng)式數(shù)據(jù)是“深層次”的劝赔。
<template>
<div class="person">
<h2>汽車信息:一臺{{ car.brand }}汽車誓焦,價值{{ car.price }}萬</h2>
<h2>游戲列表:</h2>
<ul>
<li v-for="g in games" :key="g.id">{{ g.name }}</li>
</ul>
<h2>測試:{{obj.a.b.c.d}}</h2>
<button @click="changeCarPrice">修改汽車價格</button>
<button @click="changeFirstGame">修改第一游戲</button>
<button @click="test">測試</button>
</div>
</template>
<script lang="ts" setup name="Person">
import { reactive } from 'vue'
// 數(shù)據(jù)
let car = reactive({ brand: '奔馳', price: 100 })
let games = reactive([
{ id: 'ahsgdyfa01', name: '英雄聯(lián)盟' },
{ id: 'ahsgdyfa02', name: '王者榮耀' },
{ id: 'ahsgdyfa03', name: '原神' }
])
let obj = reactive({
a:{
b:{
c:{
d:666
}
}
}
})
function changeCarPrice() {
car.price += 10
}
function changeFirstGame() {
games[0].name = '流星蝴蝶劍'
}
function test(){
obj.a.b.c.d = 999
}
</script>
3.5. 【ref 創(chuàng)建:對象類型的響應(yīng)式數(shù)據(jù)】
- 其實(shí)
ref
接收的數(shù)據(jù)可以是:基本類型、對象類型着帽。 - 若
ref
接收的是對象類型杂伟,內(nèi)部其實(shí)也是調(diào)用了reactive
函數(shù)。
<template>
<div class="person">
<h2>汽車信息:一臺{{ car.brand }}汽車仍翰,價值{{ car.price }}萬</h2>
<h2>游戲列表:</h2>
<ul>
<li v-for="g in games" :key="g.id">{{ g.name }}</li>
</ul>
<h2>測試:{{obj.a.b.c.d}}</h2>
<button @click="changeCarPrice">修改汽車價格</button>
<button @click="changeFirstGame">修改第一游戲</button>
<button @click="test">測試</button>
</div>
</template>
<script lang="ts" setup name="Person">
import { ref } from 'vue'
// 數(shù)據(jù)
let car = ref({ brand: '奔馳', price: 100 })
let games = ref([
{ id: 'ahsgdyfa01', name: '英雄聯(lián)盟' },
{ id: 'ahsgdyfa02', name: '王者榮耀' },
{ id: 'ahsgdyfa03', name: '原神' }
])
let obj = ref({
a:{
b:{
c:{
d:666
}
}
}
})
console.log(car)
function changeCarPrice() {
car.value.price += 10
}
function changeFirstGame() {
games.value[0].name = '流星蝴蝶劍'
}
function test(){
obj.value.a.b.c.d = 999
}
</script>
3.6. 【ref 對比 reactive】
宏觀角度看:
ref
用來定義:基本類型數(shù)據(jù)赫粥、對象類型數(shù)據(jù);
reactive
用來定義:對象類型數(shù)據(jù)予借。
- 區(qū)別:
ref
創(chuàng)建的變量必須使用.value
(可以使用volar
插件自動添加.value
)友瘤。
reactive
重新分配一個新對象帕棉,會失去響應(yīng)式(可以使用Object.assign
去整體替換)甜攀。
- 使用原則:
- 若需要一個基本類型的響應(yīng)式數(shù)據(jù)蚕愤,必須使用
ref
。- 若需要一個響應(yīng)式對象瀑粥,層級不深挣跋,
ref
、reactive
都可以狞换。- 若需要一個響應(yīng)式對象避咆,且層級較深,推薦使用
reactive
哀澈。
3.7. 【toRefs 與 toRef】
- 作用:將一個響應(yīng)式對象中的每一個屬性牌借,轉(zhuǎn)換為
ref
對象。 - 備注:
toRefs
與toRef
功能一致割按,但toRefs
可以批量轉(zhuǎn)換膨报。 - 語法如下:
<template>
<div class="person">
<h2>姓名:{{person.name}}</h2>
<h2>年齡:{{person.age}}</h2>
<h2>性別:{{person.gender}}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年齡</button>
<button @click="changeGender">修改性別</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {ref,reactive,toRefs,toRef} from 'vue'
// 數(shù)據(jù)
let person = reactive({name:'張三', age:18, gender:'男'})
// 通過toRefs將person對象中的n個屬性批量取出,且依然保持響應(yīng)式的能力
let {name,gender} = toRefs(person)
// 通過toRef將person對象中的gender屬性取出,且依然保持響應(yīng)式的能力
let age = toRef(person,'age')
// 方法
function changeName(){
name.value += '~'
}
function changeAge(){
age.value += 1
}
function changeGender(){
gender.value = '女'
}
</script>
3.8. 【computed】
作用:根據(jù)已有數(shù)據(jù)計算出新數(shù)據(jù)(和Vue2
中的computed
作用一致)现柠。
<template>
<div class="person">
姓:<input type="text" v-model="firstName"> <br>
名:<input type="text" v-model="lastName"> <br>
全名:<span>{{fullName}}</span> <br>
<button @click="changeFullName">全名改為:li-si</button>
</div>
</template>
<script setup lang="ts" name="App">
import {ref,computed} from 'vue'
let firstName = ref('zhang')
let lastName = ref('san')
// 計算屬性——只讀取院领,不修改
/* let fullName = computed(()=>{
return firstName.value + '-' + lastName.value
}) */
// 計算屬性——既讀取又修改
let fullName = computed({
// 讀取
get(){
return firstName.value + '-' + lastName.value
},
// 修改
set(val){
console.log('有人修改了fullName',val)
firstName.value = val.split('-')[0]
lastName.value = val.split('-')[1]
}
})
function changeFullName(){
fullName.value = 'li-si'
}
</script>
3.9.【watch】
- 作用:監(jiān)視數(shù)據(jù)的變化(和
Vue2
中的watch
作用一致) - 特點(diǎn):
Vue3
中的watch
只能監(jiān)視以下四種數(shù)據(jù):
ref
定義的數(shù)據(jù)。reactive
定義的數(shù)據(jù)够吩。- 函數(shù)返回一個值(
getter
函數(shù))比然。- 一個包含上述內(nèi)容的數(shù)組。
我們在Vue3
中使用watch
的時候周循,通常會遇到以下幾種情況:
* 情況一
監(jiān)視ref
定義的【基本類型】數(shù)據(jù):直接寫數(shù)據(jù)名即可强法,監(jiān)視的是其value
值的改變。
<template>
<div class="person">
<h1>情況一:監(jiān)視【ref】定義的【基本類型】數(shù)據(jù)</h1>
<h2>當(dāng)前求和為:{{sum}}</h2>
<button @click="changeSum">點(diǎn)我sum+1</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {ref,watch} from 'vue'
// 數(shù)據(jù)
let sum = ref(0)
// 方法
function changeSum(){
sum.value += 1
}
// 監(jiān)視湾笛,情況一:監(jiān)視【ref】定義的【基本類型】數(shù)據(jù)
const stopWatch = watch(sum,(newValue,oldValue)=>{
console.log('sum變化了',newValue,oldValue)
if(newValue >= 10){
stopWatch()
}
})
</script>
* 情況二
監(jiān)視ref
定義的【對象類型】數(shù)據(jù):直接寫數(shù)據(jù)名饮怯,監(jiān)視的是對象的【地址值】,若想監(jiān)視對象內(nèi)部的數(shù)據(jù)嚎研,要手動開啟深度監(jiān)視蓖墅。
注意:
若修改的是
ref
定義的對象中的屬性,newValue
和oldValue
都是新值临扮,因?yàn)樗鼈兪峭粋€對象论矾。若修改整個
ref
定義的對象,newValue
是新值杆勇,oldValue
是舊值贪壳,因?yàn)椴皇峭粋€對象了。
<template>
<div class="person">
<h1>情況二:監(jiān)視【ref】定義的【對象類型】數(shù)據(jù)</h1>
<h2>姓名:{{ person.name }}</h2>
<h2>年齡:{{ person.age }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年齡</button>
<button @click="changePerson">修改整個人</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {ref,watch} from 'vue'
// 數(shù)據(jù)
let person = ref({
name:'張三',
age:18
})
// 方法
function changeName(){
person.value.name += '~'
}
function changeAge(){
person.value.age += 1
}
function changePerson(){
person.value = {name:'李四',age:90}
}
/*
監(jiān)視蚜退,情況一:監(jiān)視【ref】定義的【對象類型】數(shù)據(jù)寥袭,監(jiān)視的是對象的地址值,若想監(jiān)視對象內(nèi)部屬性的變化关霸,需要手動開啟深度監(jiān)視
watch的第一個參數(shù)是:被監(jiān)視的數(shù)據(jù)
watch的第二個參數(shù)是:監(jiān)視的回調(diào)
watch的第三個參數(shù)是:配置對象(deep、immediate等等.....)
*/
watch(person,(newValue,oldValue)=>{
console.log('person變化了',newValue,oldValue)
},{deep:true})
</script>
* 情況三
監(jiān)視reactive
定義的【對象類型】數(shù)據(jù)杰扫,且默認(rèn)開啟了深度監(jiān)視队寇。
<template>
<div class="person">
<h1>情況三:監(jiān)視【reactive】定義的【對象類型】數(shù)據(jù)</h1>
<h2>姓名:{{ person.name }}</h2>
<h2>年齡:{{ person.age }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年齡</button>
<button @click="changePerson">修改整個人</button>
<hr>
<h2>測試:{{obj.a.b.c}}</h2>
<button @click="test">修改obj.a.b.c</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {reactive,watch} from 'vue'
// 數(shù)據(jù)
let person = reactive({
name:'張三',
age:18
})
let obj = reactive({
a:{
b:{
c:666
}
}
})
// 方法
function changeName(){
person.name += '~'
}
function changeAge(){
person.age += 1
}
function changePerson(){
Object.assign(person,{name:'李四',age:80})
}
function test(){
obj.a.b.c = 888
}
// 監(jiān)視,情況三:監(jiān)視【reactive】定義的【對象類型】數(shù)據(jù)章姓,且默認(rèn)是開啟深度監(jiān)視的
watch(person,(newValue,oldValue)=>{
console.log('person變化了',newValue,oldValue)
})
watch(obj,(newValue,oldValue)=>{
console.log('Obj變化了',newValue,oldValue)
})
</script>
* 情況四
監(jiān)視ref
或reactive
定義的【對象類型】數(shù)據(jù)中的某個屬性佳遣,注意點(diǎn)如下:
- 若該屬性值不是【對象類型】,需要寫成函數(shù)形式凡伊。
- 若該屬性值是依然是【對象類型】零渐,可直接編,也可寫成函數(shù)系忙,建議寫成函數(shù)诵盼。
結(jié)論:監(jiān)視的要是對象里的屬性,那么最好寫函數(shù)式,注意點(diǎn):若是對象監(jiān)視的是地址值风宁,需要關(guān)注對象內(nèi)部洁墙,需要手動開啟深度監(jiān)視。
<template>
<div class="person">
<h1>情況四:監(jiān)視【ref】或【reactive】定義的【對象類型】數(shù)據(jù)中的某個屬性</h1>
<h2>姓名:{{ person.name }}</h2>
<h2>年齡:{{ person.age }}</h2>
<h2>汽車:{{ person.car.c1 }}戒财、{{ person.car.c2 }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年齡</button>
<button @click="changeC1">修改第一臺車</button>
<button @click="changeC2">修改第二臺車</button>
<button @click="changeCar">修改整個車</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {reactive,watch} from 'vue'
// 數(shù)據(jù)
let person = reactive({
name:'張三',
age:18,
car:{
c1:'奔馳',
c2:'寶馬'
}
})
// 方法
function changeName(){
person.name += '~'
}
function changeAge(){
person.age += 1
}
function changeC1(){
person.car.c1 = '奧迪'
}
function changeC2(){
person.car.c2 = '大眾'
}
function changeCar(){
person.car = {c1:'雅迪',c2:'愛瑪'}
}
// 監(jiān)視热监,情況四:監(jiān)視響應(yīng)式對象中的某個屬性,且該屬性是基本類型的饮寞,要寫成函數(shù)式
/* watch(()=> person.name,(newValue,oldValue)=>{
console.log('person.name變化了',newValue,oldValue)
}) */
// 監(jiān)視孝扛,情況四:監(jiān)視響應(yīng)式對象中的某個屬性,且該屬性是對象類型的幽崩,可以直接寫苦始,也能寫函數(shù),更推薦寫函數(shù)
watch(()=>person.car,(newValue,oldValue)=>{
console.log('person.car變化了',newValue,oldValue)
},{deep:true})
</script>
* 情況五
監(jiān)視上述的多個數(shù)據(jù)
<template>
<div class="person">
<h1>情況五:監(jiān)視上述的多個數(shù)據(jù)</h1>
<h2>姓名:{{ person.name }}</h2>
<h2>年齡:{{ person.age }}</h2>
<h2>汽車:{{ person.car.c1 }}歉铝、{{ person.car.c2 }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年齡</button>
<button @click="changeC1">修改第一臺車</button>
<button @click="changeC2">修改第二臺車</button>
<button @click="changeCar">修改整個車</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {reactive,watch} from 'vue'
// 數(shù)據(jù)
let person = reactive({
name:'張三',
age:18,
car:{
c1:'奔馳',
c2:'寶馬'
}
})
// 方法
function changeName(){
person.name += '~'
}
function changeAge(){
person.age += 1
}
function changeC1(){
person.car.c1 = '奧迪'
}
function changeC2(){
person.car.c2 = '大眾'
}
function changeCar(){
person.car = {c1:'雅迪',c2:'愛瑪'}
}
// 監(jiān)視盈简,情況五:監(jiān)視上述的多個數(shù)據(jù)
watch([()=>person.name,person.car],(newValue,oldValue)=>{
console.log('person.car變化了',newValue,oldValue)
},{deep:true})
</script>
3.10. 【watchEffect】
官網(wǎng):立即運(yùn)行一個函數(shù),同時響應(yīng)式地追蹤其依賴太示,并在依賴更改時重新執(zhí)行該函數(shù)柠贤。
-
watch
對比watchEffect
都能監(jiān)聽響應(yīng)式數(shù)據(jù)的變化,不同的是監(jiān)聽數(shù)據(jù)變化的方式不同
watch
:要明確指出監(jiān)視的數(shù)據(jù)watchEffect
:不用明確指出監(jiān)視的數(shù)據(jù)(函數(shù)中用到哪些屬性类缤,那就監(jiān)視哪些屬性)臼勉。
-
示例代碼:
<template> <div class="person"> <h1>需求:水溫達(dá)到50℃,或水位達(dá)到20cm餐弱,則聯(lián)系服務(wù)器</h1> <h2 id="demo">水溫:{{temp}}</h2> <h2>水位:{{height}}</h2> <button @click="changePrice">水溫+1</button> <button @click="changeSum">水位+10</button> </div> </template> <script lang="ts" setup name="Person"> import {ref,watch,watchEffect} from 'vue' // 數(shù)據(jù) let temp = ref(0) let height = ref(0) // 方法 function changePrice(){ temp.value += 10 } function changeSum(){ height.value += 1 } // 用watch實(shí)現(xiàn)宴霸,需要明確的指出要監(jiān)視:temp、height watch([temp,height],(value)=>{ // 從value中獲取最新的temp值膏蚓、height值 const [newTemp,newHeight] = value // 室溫達(dá)到50℃瓢谢,或水位達(dá)到20cm,立刻聯(lián)系服務(wù)器 if(newTemp >= 50 || newHeight >= 20){ console.log('聯(lián)系服務(wù)器') } }) // 用watchEffect實(shí)現(xiàn)驮瞧,不用 const stopWtach = watchEffect(()=>{ // 室溫達(dá)到50℃氓扛,或水位達(dá)到20cm,立刻聯(lián)系服務(wù)器 if(temp.value >= 50 || height.value >= 20){ console.log(document.getElementById('demo')?.innerText) console.log('聯(lián)系服務(wù)器') } // 水溫達(dá)到100论笔,或水位達(dá)到50采郎,取消監(jiān)視 if(temp.value === 100 || height.value === 50){ console.log('清理了') stopWtach() } }) </script>
3.11. 【標(biāo)簽的 ref 屬性】
作用:用于注冊模板引用。
用在普通
DOM
標(biāo)簽上狂魔,獲取的是DOM
節(jié)點(diǎn)蒜埋。用在組件標(biāo)簽上,獲取的是組件實(shí)例對象最楷。
用在普通DOM
標(biāo)簽上:
<template>
<div class="person">
<h1 ref="title1">尚硅谷</h1>
<h2 ref="title2">前端</h2>
<h3 ref="title3">Vue</h3>
<input type="text" ref="inpt"> <br><br>
<button @click="showLog">點(diǎn)我打印內(nèi)容</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {ref} from 'vue'
let title1 = ref()
let title2 = ref()
let title3 = ref()
function showLog(){
// 通過id獲取元素
const t1 = document.getElementById('title1')
// 打印內(nèi)容
console.log((t1 as HTMLElement).innerText)
console.log((<HTMLElement>t1).innerText)
console.log(t1?.innerText)
/************************************/
// 通過ref獲取元素
console.log(title1.value)
console.log(title2.value)
console.log(title3.value)
}
</script>
用在組件標(biāo)簽上:
<!-- 父組件App.vue -->
<template>
<Person ref="ren"/>
<button @click="test">測試</button>
</template>
<script lang="ts" setup name="App">
import Person from './components/Person.vue'
import {ref} from 'vue'
let ren = ref()
function test(){
console.log(ren.value.name)
console.log(ren.value.age)
}
</script>
<!-- 子組件Person.vue中要使用defineExpose暴露內(nèi)容 -->
<script lang="ts" setup name="Person">
import {ref,defineExpose} from 'vue'
// 數(shù)據(jù)
let name = ref('張三')
let age = ref(18)
/****************************/
/****************************/
// 使用defineExpose將組件中的數(shù)據(jù)交給外部
defineExpose({name,age})
</script>
3.12. 【props】
// 定義一個接口整份,限制每個Person對象的格式 export interface PersonInter { id:string, name:string, age:number } // 定義一個自定義類型Persons export type Persons = Array<PersonInter>
App.vue
中代碼:<template> <Person :list="persons"/> </template> <script lang="ts" setup name="App"> import Person from './components/Person.vue' import {reactive} from 'vue' import {type Persons} from './types' let persons = reactive<Persons>([ {id:'e98219e12',name:'張三',age:18}, {id:'e98219e13',name:'李四',age:19}, {id:'e98219e14',name:'王五',age:20} ]) </script>
Person.vue
中代碼:<template> <div class="person"> <ul> <li v-for="item in list" :key="item.id"> {{item.name}}--{{item.age}} </li> </ul> </div> </template> <script lang="ts" setup name="Person"> import {defineProps} from 'vue' import {type PersonInter} from '@/types' // 第一種寫法:僅接收 // const props = defineProps(['list']) // 第二種寫法:接收+限制類型 // defineProps<{list:Persons}>() // 第三種寫法:接收+限制類型+指定默認(rèn)值+限制必要性 let props = withDefaults(defineProps<{list?:Persons}>(),{ list:()=>[{id:'asdasg01',name:'小豬佩奇',age:18}] }) console.log(props) </script>
3.13. 【生命周期】
概念:
Vue
組件實(shí)例在創(chuàng)建時要經(jīng)歷一系列的初始化步驟待错,在此過程中Vue
會在合適的時機(jī),調(diào)用特定的函數(shù)皂林,從而讓開發(fā)者有機(jī)會在特定階段運(yùn)行自己的代碼朗鸠,這些特定的函數(shù)統(tǒng)稱為:生命周期鉤子-
規(guī)律:
生命周期整體分為四個階段,分別是:創(chuàng)建础倍、掛載烛占、更新、銷毀沟启,每個階段都有兩個鉤子忆家,一前一后。
-
Vue2
的生命周期創(chuàng)建階段:
beforeCreate
德迹、created
掛載階段:
beforeMount
芽卿、mounted
更新階段:
beforeUpdate
、updated
銷毀階段:
beforeDestroy
胳搞、destroyed
-
Vue3
的生命周期創(chuàng)建階段:
setup
掛載階段:
onBeforeMount
卸例、onMounted
更新階段:
onBeforeUpdate
、onUpdated
卸載階段:
onBeforeUnmount
肌毅、onUnmounted
常用的鉤子:
onMounted
(掛載完畢)筷转、onUpdated
(更新完畢)、onBeforeUnmount
(卸載之前)-
示例代碼:
<template> <div class="person"> <h2>當(dāng)前求和為:{{ sum }}</h2> <button @click="changeSum">點(diǎn)我sum+1</button> </div> </template> <!-- vue3寫法 --> <script lang="ts" setup name="Person"> import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue' // 數(shù)據(jù) let sum = ref(0) // 方法 function changeSum() { sum.value += 1 } console.log('setup') // 生命周期鉤子 onBeforeMount(()=>{ console.log('掛載之前') }) onMounted(()=>{ console.log('掛載完畢') }) onBeforeUpdate(()=>{ console.log('更新之前') }) onUpdated(()=>{ console.log('更新完畢') }) onBeforeUnmount(()=>{ console.log('卸載之前') }) onUnmounted(()=>{ console.log('卸載完畢') }) </script>
3.14. 【自定義hook】
什么是
hook
悬而?—— 本質(zhì)是一個函數(shù)呜舒,把setup
函數(shù)中使用的Composition API
進(jìn)行了封裝,類似于vue2.x
中的mixin
笨奠。自定義
hook
的優(yōu)勢:復(fù)用代碼, 讓setup
中的邏輯更清楚易懂袭蝗。
示例代碼:
-
useSum.ts
中內(nèi)容如下:import {ref,onMounted} from 'vue' export default function(){ let sum = ref(0) const increment = ()=>{ sum.value += 1 } const decrement = ()=>{ sum.value -= 1 } onMounted(()=>{ increment() }) //向外部暴露數(shù)據(jù) return {sum,increment,decrement} }
-
useDog.ts
中內(nèi)容如下:import {reactive,onMounted} from 'vue' import axios,{AxiosError} from 'axios' export default function(){ let dogList = reactive<string[]>([]) // 方法 async function getDog(){ try { // 發(fā)請求 let {data} = await axios.get('https://dog.ceo/api/breed/pembroke/images/random') // 維護(hù)數(shù)據(jù) dogList.push(data.message) } catch (error) { // 處理錯誤 const err = <AxiosError>error console.log(err.message) } } // 掛載鉤子 onMounted(()=>{ getDog() }) //向外部暴露數(shù)據(jù) return {dogList,getDog} }
-
組件中具體使用:
<template> <h2>當(dāng)前求和為:{{sum}}</h2> <button @click="increment">點(diǎn)我+1</button> <button @click="decrement">點(diǎn)我-1</button> <hr> <img v-for="(u,index) in dogList.urlList" :key="index" :src="(u as string)"> <span v-show="dogList.isLoading">加載中......</span><br> <button @click="getDog">再來一只狗</button> </template> <script lang="ts"> import {defineComponent} from 'vue' export default defineComponent({ name:'App', }) </script> <script setup lang="ts"> import useSum from './hooks/useSum' import useDog from './hooks/useDog' let {sum,increment,decrement} = useSum() let {dogList,getDog} = useDog() </script>
4. 路由
4.1. 【對路由的理解】
4.2. 【基本切換效果】
Vue3
中要使用vue-router
的最新版本,目前是4
版本般婆。-
路由配置文件代碼如下:
import {createRouter,createWebHistory} from 'vue-router' import Home from '@/pages/Home.vue' import News from '@/pages/News.vue' import About from '@/pages/About.vue' const router = createRouter({ history:createWebHistory(), routes:[ { path:'/home', component:Home }, { path:'/about', component:About } ] }) export default router
-
main.ts
代碼如下:import router from './router/index' app.use(router) app.mount('#app')
-
App.vue
代碼如下<template> <div class="app"> <h2 class="title">Vue路由測試</h2> <!-- 導(dǎo)航區(qū) --> <div class="navigate"> <RouterLink to="/home" active-class="active">首頁</RouterLink> <RouterLink to="/news" active-class="active">新聞</RouterLink> <RouterLink to="/about" active-class="active">關(guān)于</RouterLink> </div> <!-- 展示區(qū) --> <div class="main-content"> <RouterView></RouterView> </div> </div> </template> <script lang="ts" setup name="App"> import {RouterLink,RouterView} from 'vue-router' </script>
4.3. 【兩個注意點(diǎn)】
路由組件通常存放在
pages
或views
文件夾到腥,一般組件通常存放在components
文件夾。通過點(diǎn)擊導(dǎo)航蔚袍,視覺效果上“消失” 了的路由組件左电,默認(rèn)是被卸載掉的,需要的時候再去掛載页响。
4.4.【路由器工作模式】
-
history
模式優(yōu)點(diǎn):
URL
更加美觀,不帶有#
段誊,更接近傳統(tǒng)的網(wǎng)站URL
闰蚕。缺點(diǎn):后期項(xiàng)目上線,需要服務(wù)端配合處理路徑問題连舍,否則刷新會有
404
錯誤没陡。const router = createRouter({ history:createWebHistory(), //history模式 /******/ })
-
hash
模式優(yōu)點(diǎn):兼容性更好,因?yàn)椴恍枰?wù)器端處理路徑。
缺點(diǎn):
URL
帶有#
不太美觀盼玄,且在SEO
優(yōu)化方面相對較差贴彼。const router = createRouter({ history:createWebHashHistory(), //hash模式 /******/ })
4.5. 【to的兩種寫法】
<!-- 第一種:to的字符串寫法 -->
<router-link active-class="active" to="/home">主頁</router-link>
<!-- 第二種:to的對象寫法 -->
<router-link active-class="active" :to="{path:'/home'}">Home</router-link>
4.6. 【命名路由】
作用:可以簡化路由跳轉(zhuǎn)及傳參(后面就講)。
給路由規(guī)則命名:
routes:[
{
name:'zhuye',
path:'/home',
component:Home
},
{
name:'xinwen',
path:'/news',
component:News,
},
{
name:'guanyu',
path:'/about',
component:About
}
]
跳轉(zhuǎn)路由:
<!--簡化前:需要寫完整的路徑(to的字符串寫法) -->
<router-link to="/news/detail">跳轉(zhuǎn)</router-link>
<!--簡化后:直接通過名字跳轉(zhuǎn)(to的對象寫法配合name屬性) -->
<router-link :to="{name:'guanyu'}">跳轉(zhuǎn)</router-link>
4.7. 【嵌套路由】
編寫
News
的子路由:Detail.vue
-
配置路由規(guī)則埃儿,使用
children
配置項(xiàng):const router = createRouter({ history:createWebHistory(), routes:[ { name:'zhuye', path:'/home', component:Home }, { name:'xinwen', path:'/news', component:News, children:[ { name:'xiang', path:'detail', component:Detail } ] }, { name:'guanyu', path:'/about', component:About } ] }) export default router
-
跳轉(zhuǎn)路由(記得要加完整路徑):
<router-link to="/news/detail">xxxx</router-link> <!-- 或 --> <router-link :to="{path:'/news/detail'}">xxxx</router-link>
-
記得去
Home
組件中預(yù)留一個<router-view>
<template> <div class="news"> <nav class="news-list"> <RouterLink v-for="news in newsList" :key="news.id" :to="{path:'/news/detail'}"> {{news.name}} </RouterLink> </nav> <div class="news-detail"> <RouterView/> </div> </div> </template>
4.8. 【路由傳參】
query參數(shù)
-
傳遞參數(shù)
<!-- 跳轉(zhuǎn)并攜帶query參數(shù)(to的字符串寫法) --> <router-link to="/news/detail?a=1&b=2&content=歡迎你"> 跳轉(zhuǎn) </router-link> <!-- 跳轉(zhuǎn)并攜帶query參數(shù)(to的對象寫法) --> <RouterLink :to="{ //name:'xiang', //用name也可以跳轉(zhuǎn) path:'/news/detail', query:{ id:news.id, title:news.title, content:news.content } }" > {{news.title}} </RouterLink>
-
接收參數(shù):
import {useRoute} from 'vue-router' const route = useRoute() // 打印query參數(shù) console.log(route.query)
params參數(shù)
-
傳遞參數(shù)
<!-- 跳轉(zhuǎn)并攜帶params參數(shù)(to的字符串寫法) --> <RouterLink :to="`/news/detail/001/新聞001/內(nèi)容001`">{{news.title}}</RouterLink> <!-- 跳轉(zhuǎn)并攜帶params參數(shù)(to的對象寫法) --> <RouterLink :to="{ name:'xiang', //用name跳轉(zhuǎn) params:{ id:news.id, title:news.title, content:news.title } }" > {{news.title}} </RouterLink>
-
接收參數(shù):
import {useRoute} from 'vue-router' const route = useRoute() // 打印params參數(shù) console.log(route.params)
備注1:傳遞
params
參數(shù)時器仗,若使用to
的對象寫法,必須使用name
配置項(xiàng)童番,不能用path
精钮。備注2:傳遞
params
參數(shù)時,需要提前在規(guī)則中占位剃斧。
4.9. 【路由的props配置】
作用:讓路由組件更方便的收到參數(shù)(可以將路由參數(shù)作為props
傳給組件)
{
name:'xiang',
path:'detail/:id/:title/:content',
component:Detail,
// props的對象寫法轨香,作用:把對象中的每一組key-value作為props傳給Detail組件
// props:{a:1,b:2,c:3},
// props的布爾值寫法,作用:把收到了每一組params參數(shù)幼东,作為props傳給Detail組件
// props:true
// props的函數(shù)寫法臂容,作用:把返回的對象中每一組key-value作為props傳給Detail組件
props(route){
return route.query
}
}
4.10. 【 replace屬性】
作用:控制路由跳轉(zhuǎn)時操作瀏覽器歷史記錄的模式。
-
瀏覽器的歷史記錄有兩種寫入方式:分別為
push
和replace
:-
push
是追加歷史記錄(默認(rèn)值)根蟹。 -
replace
是替換當(dāng)前記錄脓杉。
-
-
開啟
replace
模式:<RouterLink replace .......>News</RouterLink>
4.11. 【編程式導(dǎo)航】
路由組件的兩個重要的屬性:$route
和$router
變成了兩個hooks
import {useRoute,useRouter} from 'vue-router'
const route = useRoute()
const router = useRouter()
console.log(route.query)
console.log(route.parmas)
console.log(router.push)
console.log(router.replace)
4.12. 【重定向】
作用:將特定的路徑,重新定向到已有路由娜亿。
-
具體編碼:
{ path:'/', redirect:'/about' }
5. pinia
5.1【準(zhǔn)備一個效果】
5.2【搭建 pinia 環(huán)境】
第一步:npm install pinia
第二步:操作src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
/* 引入createPinia丽已,用于創(chuàng)建pinia */
import { createPinia } from 'pinia'
/* 創(chuàng)建pinia */
const pinia = createPinia()
const app = createApp(App)
/* 使用插件 */{}
app.use(pinia)
app.mount('#app')
此時開發(fā)者工具中已經(jīng)有了pinia
選項(xiàng)
5.3【存儲+讀取數(shù)據(jù)】
Store
是一個保存:狀態(tài)、業(yè)務(wù)邏輯 的實(shí)體买决,每個組件都可以讀取沛婴、寫入它。它有三個概念:
state
督赤、getter
嘁灯、action
,相當(dāng)于組件中的:data
躲舌、computed
和methods
丑婿。-
具體編碼:
src/store/count.ts
// 引入defineStore用于創(chuàng)建store import {defineStore} from 'pinia' // 定義并暴露一個store export const useCountStore = defineStore('count',{ // 動作 actions:{}, // 狀態(tài) state(){ return { sum:6 } }, // 計算 getters:{} })
-
具體編碼:
src/store/talk.ts
// 引入defineStore用于創(chuàng)建store import {defineStore} from 'pinia' // 定義并暴露一個store export const useTalkStore = defineStore('talk',{ // 動作 actions:{}, // 狀態(tài) state(){ return { talkList:[ {id:'yuysada01',content:'你今天有點(diǎn)怪,哪里怪没卸?怪好看的羹奉!'}, {id:'yuysada02',content:'草莓、藍(lán)莓约计、蔓越莓诀拭,你想我了沒?'}, {id:'yuysada03',content:'心里給你留了一塊地煤蚌,我的死心塌地'} ] } }, // 計算 getters:{} })
-
組件中使用
state
中的數(shù)據(jù)<template> <h2>當(dāng)前求和為:{{ sumStore.sum }}</h2> </template> <script setup lang="ts" name="Count"> // 引入對應(yīng)的useXxxxxStore import {useSumStore} from '@/store/sum' // 調(diào)用useXxxxxStore得到對應(yīng)的store const sumStore = useSumStore() </script>
<template> <ul> <li v-for="talk in talkStore.talkList" :key="talk.id"> {{ talk.content }} </li> </ul> </template> <script setup lang="ts" name="Count"> import axios from 'axios' import {useTalkStore} from '@/store/talk' const talkStore = useTalkStore() </script>
5.4.【修改數(shù)據(jù)】(三種方式)
-
第一種修改方式耕挨,直接修改
countStore.sum = 666
-
第二種修改方式:批量修改
countStore.$patch({ sum:999, school:'atguigu' })
-
第三種修改方式:借助
action
修改(action
中可以編寫一些業(yè)務(wù)邏輯)import { defineStore } from 'pinia' export const useCountStore = defineStore('count', { /*************/ actions: { //加 increment(value:number) { if (this.sum < 10) { //操作countStore中的sum this.sum += value } }, //減 decrement(value:number){ if(this.sum > 1){ this.sum -= value } } }, /*************/ })
-
組件中調(diào)用
action
即可// 使用countStore const countStore = useCountStore() // 調(diào)用對應(yīng)action countStore.incrementOdd(n.value)
5.5.【storeToRefs】
- 借助
storeToRefs
將store
中的數(shù)據(jù)轉(zhuǎn)為ref
對象细卧,方便在模板中使用。 - 注意:
pinia
提供的storeToRefs
只會將數(shù)據(jù)做轉(zhuǎn)換筒占,而Vue
的toRefs
會轉(zhuǎn)換store
中數(shù)據(jù)贪庙。
<template>
<div class="count">
<h2>當(dāng)前求和為:{{sum}}</h2>
</div>
</template>
<script setup lang="ts" name="Count">
import { useCountStore } from '@/store/count'
/* 引入storeToRefs */
import { storeToRefs } from 'pinia'
/* 得到countStore */
const countStore = useCountStore()
/* 使用storeToRefs轉(zhuǎn)換countStore,隨后解構(gòu) */
const {sum} = storeToRefs(countStore)
</script>
5.6.【getters】
概念:當(dāng)
state
中的數(shù)據(jù)翰苫,需要經(jīng)過處理后再使用時止邮,可以使用getters
配置。-
追加
getters
配置革骨。// 引入defineStore用于創(chuàng)建store import {defineStore} from 'pinia' // 定義并暴露一個store export const useCountStore = defineStore('count',{ // 動作 actions:{ /************/ }, // 狀態(tài) state(){ return { sum:1, school:'atguigu' } }, // 計算 getters:{ bigSum:(state):number => state.sum *10, upperSchool():string{ return this. school.toUpperCase() } } })
-
組件中讀取數(shù)據(jù):
const {increment,decrement} = countStore let {sum,school,bigSum,upperSchool} = storeToRefs(countStore)
5.7.【$subscribe】
通過 store 的 $subscribe()
方法偵聽 state
及其變化
talkStore.$subscribe((mutate,state)=>{
console.log('LoveTalk',mutate,state)
localStorage.setItem('talk',JSON.stringify(talkList.value))
})
5.8. 【store組合式寫法】
import {defineStore} from 'pinia'
import axios from 'axios'
import {nanoid} from 'nanoid'
import {reactive} from 'vue'
export const useTalkStore = defineStore('talk',()=>{
// talkList就是state
const talkList = reactive(
JSON.parse(localStorage.getItem('talkList') as string) || []
)
// getATalk函數(shù)相當(dāng)于action
async function getATalk(){
// 發(fā)請求农尖,下面這行的寫法是:連續(xù)解構(gòu)賦值+重命名
let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
// 把請求回來的字符串,包裝成一個對象
let obj = {id:nanoid(),title}
// 放到數(shù)組中
talkList.unshift(obj)
}
return {talkList,getATalk}
})
6. 組件通信
Vue3
組件通信和Vue2
的區(qū)別:
- 移出事件總線良哲,使用
mitt
代替盛卡。
-
vuex
換成了pinia
。 - 把
.sync
優(yōu)化到了v-model
里面了筑凫。 - 把
$listeners
所有的東西滑沧,合并到$attrs
中了。 -
$children
被砍掉了巍实。
常見搭配形式:
6.1. 【props】
概述:props
是使用頻率最高的一種通信方式滓技,常用與 :父 ? 子。
- 若 父傳子:屬性值是非函數(shù)棚潦。
- 若 子傳父:屬性值是函數(shù)令漂。
父組件:
<template>
<div class="father">
<h3>父組件,</h3>
<h4>我的車:{{ car }}</h4>
<h4>兒子給的玩具:{{ toy }}</h4>
<Child :car="car" :getToy="getToy"/>
</div>
</template>
<script setup lang="ts" name="Father">
import Child from './Child.vue'
import { ref } from "vue";
// 數(shù)據(jù)
const car = ref('奔馳')
const toy = ref()
// 方法
function getToy(value:string){
toy.value = value
}
</script>
子組件
<template>
<div class="child">
<h3>子組件</h3>
<h4>我的玩具:{{ toy }}</h4>
<h4>父給我的車:{{ car }}</h4>
<button @click="getToy(toy)">玩具給父親</button>
</div>
</template>
<script setup lang="ts" name="Child">
import { ref } from "vue";
const toy = ref('奧特曼')
defineProps(['car','getToy'])
</script>
6.2. 【自定義事件】
- 概述:自定義事件常用于:子 => 父丸边。
- 注意區(qū)分好:原生事件叠必、自定義事件。
- 原生事件:
- 事件名是特定的(
click
妹窖、mosueenter
等等) - 事件對象
$event
: 是包含事件相關(guān)信息的對象(pageX
纬朝、pageY
、target
骄呼、keyCode
)
- 事件名是特定的(
- 自定義事件:
- 事件名是任意名稱
- <strong style="color:red">事件對象
$event
: 是調(diào)用emit
時所提供的數(shù)據(jù)共苛,可以是任意類型!r烟选隅茎!</strong >
-
示例:
<!--在父組件中,給子組件綁定自定義事件:--> <Child @send-toy="toy = $event"/> <!--注意區(qū)分原生事件與自定義事件中的$event--> <button @click="toy = $event">測試</button>
//子組件中嫉沽,觸發(fā)事件: this.$emit('send-toy', 具體數(shù)據(jù))
6.3. 【mitt】
概述:與消息訂閱與發(fā)布(pubsub
)功能類似患膛,可以實(shí)現(xiàn)任意組件間通信。
安裝mitt
npm i mitt
新建文件:src\utils\emitter.ts
// 引入mitt
import mitt from "mitt";
// 創(chuàng)建emitter
const emitter = mitt()
/*
// 綁定事件
emitter.on('abc',(value)=>{
console.log('abc事件被觸發(fā)',value)
})
emitter.on('xyz',(value)=>{
console.log('xyz事件被觸發(fā)',value)
})
setInterval(() => {
// 觸發(fā)事件
emitter.emit('abc',666)
emitter.emit('xyz',777)
}, 1000);
setTimeout(() => {
// 清理事件
emitter.all.clear()
}, 3000);
*/
// 創(chuàng)建并暴露mitt
export default emitter
接收數(shù)據(jù)的組件中:綁定事件耻蛇、同時在銷毀前解綁事件:
import emitter from "@/utils/emitter";
import { onUnmounted } from "vue";
// 綁定事件
emitter.on('send-toy',(value)=>{
console.log('send-toy事件被觸發(fā)',value)
})
onUnmounted(()=>{
// 解綁事件
emitter.off('send-toy')
})
【第三步】:提供數(shù)據(jù)的組件踪蹬,在合適的時候觸發(fā)事件
import emitter from "@/utils/emitter";
function sendToy(){
// 觸發(fā)事件
emitter.emit('send-toy',toy.value)
}
注意這個重要的內(nèi)置關(guān)系,總線依賴著這個內(nèi)置關(guān)系
6.4.【v-model】
概述:實(shí)現(xiàn) 父?子 之間相互通信臣咖。
-
前序知識 ——
v-model
的本質(zhì)<!-- 使用v-model指令 --> <input type="text" v-model="userName"> <!-- v-model的本質(zhì)是下面這行代碼 --> <input type="text" :value="userName" @input="userName =(<HTMLInputElement>$event.target).value" >
-
組件標(biāo)簽上的
v-model
的本質(zhì)::moldeValue
+update:modelValue
事件跃捣。<!-- 組件標(biāo)簽上使用v-model指令 --> <AtguiguInput v-model="userName"/> <!-- 組件標(biāo)簽上v-model的本質(zhì) --> <AtguiguInput :modelValue="userName" @update:model-value="userName = $event"/>
AtguiguInput
組件中:<template> <div class="box"> <!--將接收的value值賦給input元素的value屬性,目的是:為了呈現(xiàn)數(shù)據(jù) --> <!--給input元素綁定原生input事件夺蛇,觸發(fā)input事件時疚漆,進(jìn)而觸發(fā)update:model-value事件--> <input type="text" :value="modelValue" @input="emit('update:model-value',$event.target.value)" > </div> </template> <script setup lang="ts" name="AtguiguInput"> // 接收props defineProps(['modelValue']) // 聲明事件 const emit = defineEmits(['update:model-value']) </script>
-
也可以更換
value
,例如改成abc
<!-- 也可以更換value刁赦,例如改成abc--> <AtguiguInput v-model:abc="userName"/> <!-- 上面代碼的本質(zhì)如下 --> <AtguiguInput :abc="userName" @update:abc="userName = $event"/>
AtguiguInput
組件中:<template> <div class="box"> <input type="text" :value="abc" @input="emit('update:abc',$event.target.value)" > </div> </template> <script setup lang="ts" name="AtguiguInput"> // 接收props defineProps(['abc']) // 聲明事件 const emit = defineEmits(['update:abc']) </script>
-
如果
value
可以更換娶聘,那么就可以在組件標(biāo)簽上多次使用v-model
<AtguiguInput v-model:abc="userName" v-model:xyz="password"/>
6.5.【$attrs 】
概述:
$attrs
用于實(shí)現(xiàn)當(dāng)前組件的父組件,向當(dāng)前組件的子組件通信(祖→孫)甚脉。-
具體說明:
$attrs
是一個對象丸升,包含所有父組件傳入的標(biāo)簽屬性。注意:
$attrs
會自動排除props
中聲明的屬性(可以認(rèn)為聲明過的props
被子組件自己“消費(fèi)”了)
父組件:
<template>
<div class="father">
<h3>父組件</h3>
<Child :a="a" :b="b" :c="c" :d="d" v-bind="{x:100,y:200}" :updateA="updateA"/>
</div>
</template>
<script setup lang="ts" name="Father">
import Child from './Child.vue'
import { ref } from "vue";
let a = ref(1)
let b = ref(2)
let c = ref(3)
let d = ref(4)
function updateA(value){
a.value = value
}
</script>
子組件:
<template>
<div class="child">
<h3>子組件</h3>
<GrandChild v-bind="$attrs"/>
</div>
</template>
<script setup lang="ts" name="Child">
import GrandChild from './GrandChild.vue'
</script>
孫組件:
<template>
<div class="grand-child">
<h3>孫組件</h3>
<h4>a:{{ a }}</h4>
<h4>b:{{ b }}</h4>
<h4>c:{{ c }}</h4>
<h4>d:{{ d }}</h4>
<h4>x:{{ x }}</h4>
<h4>y:{{ y }}</h4>
<button @click="updateA(666)">點(diǎn)我更新A</button>
</div>
</template>
<script setup lang="ts" name="GrandChild">
defineProps(['a','b','c','d','x','y','updateA'])
</script>
6.6. 【parent】
-
概述:
-
$refs
用于 :父→子狡耻。 -
$parent
用于:子→父。
-
-
原理如下:
屬性 說明 $refs
值為對象猴凹,包含所有被 ref
屬性標(biāo)識的DOM
元素或組件實(shí)例夷狰。$parent
值為對象,當(dāng)前組件的父組件實(shí)例對象郊霎。
6.7. 【provide沼头、inject】
概述:實(shí)現(xiàn)祖孫組件直接通信
-
具體使用:
- 在祖先組件中通過
provide
配置向后代組件提供數(shù)據(jù) - 在后代組件中通過
inject
配置來聲明接收數(shù)據(jù)
- 在祖先組件中通過
-
具體編碼:
【第一步】父組件中,使用
provide
提供數(shù)據(jù)<template> <div class="father"> <h3>父組件</h3> <h4>資產(chǎn):{{ money }}</h4> <h4>汽車:{{ car }}</h4> <button @click="money += 1">資產(chǎn)+1</button> <button @click="car.price += 1">汽車價格+1</button> <Child/> </div> </template> <script setup lang="ts" name="Father"> import Child from './Child.vue' import { ref,reactive,provide } from "vue"; // 數(shù)據(jù) let money = ref(100) let car = reactive({ brand:'奔馳', price:100 }) // 用于更新money的方法 function updateMoney(value:number){ money.value += value } // 提供數(shù)據(jù) provide('moneyContext',{money,updateMoney}) provide('car',car) </script>
注意:子組件中不用編寫任何東西书劝,是不受到任何打擾的
【第二步】孫組件中使用
inject
配置項(xiàng)接受數(shù)據(jù)进倍。<template> <div class="grand-child"> <h3>我是孫組件</h3> <h4>資產(chǎn):{{ money }}</h4> <h4>汽車:{{ car }}</h4> <button @click="updateMoney(6)">點(diǎn)我</button> </div> </template> <script setup lang="ts" name="GrandChild"> import { inject } from 'vue'; // 注入數(shù)據(jù) let {money,updateMoney} = inject('moneyContext',{money:0,updateMoney:(x:number)=>{}}) let car = inject('car') </script>
6.8. 【pinia】
參考之前pinia
部分的講解
6.9. 【slot】
1. 默認(rèn)插槽
父組件中:
<Category title="今日熱門游戲">
<ul>
<li v-for="g in games" :key="g.id">{{ g.name }}</li>
</ul>
</Category>
子組件中:
<template>
<div class="item">
<h3>{{ title }}</h3>
<!-- 默認(rèn)插槽 -->
<slot></slot>
</div>
</template>
2. 具名插槽
父組件中:
<Category title="今日熱門游戲">
<template v-slot:s1>
<ul>
<li v-for="g in games" :key="g.id">{{ g.name }}</li>
</ul>
</template>
<template #s2>
<a href="">更多</a>
</template>
</Category>
子組件中:
<template>
<div class="item">
<h3>{{ title }}</h3>
<slot name="s1"></slot>
<slot name="s2"></slot>
</div>
</template>
3. 作用域插槽
理解:<span style="color:red">數(shù)據(jù)在組件的自身,但根據(jù)數(shù)據(jù)生成的結(jié)構(gòu)需要組件的使用者來決定庄撮。</span>(新聞數(shù)據(jù)在
News
組件中背捌,但使用數(shù)據(jù)所遍歷出來的結(jié)構(gòu)由App
組件決定)-
具體編碼:
父組件中: <Game v-slot="params"> <!-- <Game v-slot:default="params"> --> <!-- <Game #default="params"> --> <ul> <li v-for="g in params.games" :key="g.id">{{ g.name }}</li> </ul> </Game> 子組件中: <template> <div class="category"> <h2>今日游戲榜單</h2> <slot :games="games" a="哈哈"></slot> </div> </template> <script setup lang="ts" name="Category"> import {reactive} from 'vue' let games = reactive([ {id:'asgdytsa01',name:'英雄聯(lián)盟'}, {id:'asgdytsa02',name:'王者榮耀'}, {id:'asgdytsa03',name:'紅色警戒'}, {id:'asgdytsa04',name:'斗羅大陸'} ]) </script>
7. 其它 API
7.1.【shallowRef 與 shallowReactive 】
shallowRef
作用:創(chuàng)建一個響應(yīng)式數(shù)據(jù),但只對頂層屬性進(jìn)行響應(yīng)式處理洞斯。
-
用法:
let myVar = shallowRef(initialValue);
特點(diǎn):只跟蹤引用值的變化毡庆,不關(guān)心值內(nèi)部的屬性變化。
shallowReactive
作用:創(chuàng)建一個淺層響應(yīng)式對象烙如,只會使對象的最頂層屬性變成響應(yīng)式的么抗,對象內(nèi)部的嵌套屬性則不會變成響應(yīng)式的
-
用法:
const myObj = shallowReactive({ ... });
特點(diǎn):對象的頂層屬性是響應(yīng)式的,但嵌套對象的屬性不是亚铁。
總結(jié)
通過使用
shallowRef()
和shallowReactive()
來繞開深度響應(yīng)蝇刀。淺層式API
創(chuàng)建的狀態(tài)只在其頂層是響應(yīng)式的,對所有深層的對象不會做任何處理徘溢,避免了對每一個內(nèi)部屬性做響應(yīng)式所帶來的性能成本吞琐,這使得屬性的訪問變得更快捆探,可提升性能。
7.2.【readonly 與 shallowReadonly】
readonly
作用:用于創(chuàng)建一個對象的深只讀副本站粟。
-
用法:
const original = reactive({ ... }); const readOnlyCopy = readonly(original);
-
特點(diǎn):
- 對象的所有嵌套屬性都將變?yōu)橹蛔x黍图。
- 任何嘗試修改這個對象的操作都會被阻止(在開發(fā)模式下,還會在控制臺中發(fā)出警告)奴烙。
-
應(yīng)用場景:
- 創(chuàng)建不可變的狀態(tài)快照助被。
- 保護(hù)全局狀態(tài)或配置不被修改。
shallowReadonly
作用:與
readonly
類似切诀,但只作用于對象的頂層屬性揩环。-
用法:
const original = reactive({ ... }); const shallowReadOnlyCopy = shallowReadonly(original);
-
特點(diǎn):
只將對象的頂層屬性設(shè)置為只讀,對象內(nèi)部的嵌套屬性仍然是可變的幅虑。
適用于只需保護(hù)對象頂層屬性的場景丰滑。
7.3.【toRaw 與 markRaw】
toRaw
-
作用:用于獲取一個響應(yīng)式對象的原始對象,
toRaw
返回的對象不再是響應(yīng)式的翘单,不會觸發(fā)視圖更新吨枉。官網(wǎng)描述:這是一個可以用于臨時讀取而不引起代理訪問/跟蹤開銷,或是寫入而不觸發(fā)更改的特殊方法哄芜。不建議保存對原始對象的持久引用貌亭,請謹(jǐn)慎使用。
何時使用认臊? —— 在需要將響應(yīng)式對象傳遞給非
Vue
的庫或外部系統(tǒng)時圃庭,使用toRaw
可以確保它們收到的是普通對象 -
具體編碼:
import { reactive,toRaw,markRaw,isReactive } from "vue"; /* toRaw */ // 響應(yīng)式對象 let person = reactive({name:'tony',age:18}) // 原始對象 let rawPerson = toRaw(person) /* markRaw */ let citysd = markRaw([ {id:'asdda01',name:'北京'}, {id:'asdda02',name:'上海'}, {id:'asdda03',name:'天津'}, {id:'asdda04',name:'重慶'} ]) // 根據(jù)原始對象citys去創(chuàng)建響應(yīng)式對象citys2 —— 創(chuàng)建失敗,因?yàn)閏itys被markRaw標(biāo)記了 let citys2 = reactive(citys) console.log(isReactive(person)) console.log(isReactive(rawPerson)) console.log(isReactive(citys)) console.log(isReactive(citys2))
markRaw
-
作用:標(biāo)記一個對象失晴,使其永遠(yuǎn)不會變成響應(yīng)式的剧腻。
例如使用
mockjs
時,為了防止誤把mockjs
變?yōu)轫憫?yīng)式對象涂屁,可以使用markRaw
去標(biāo)記mockjs
-
編碼:
/* markRaw */ let citys = markRaw([ {id:'asdda01',name:'北京'}, {id:'asdda02',name:'上海'}, {id:'asdda03',name:'天津'}, {id:'asdda04',name:'重慶'} ]) // 根據(jù)原始對象citys去創(chuàng)建響應(yīng)式對象citys2 —— 創(chuàng)建失敗书在,因?yàn)閏itys被markRaw標(biāo)記了 let citys2 = reactive(citys)
7.4.【customRef】
作用:創(chuàng)建一個自定義的ref
,并對其依賴項(xiàng)跟蹤和更新觸發(fā)進(jìn)行邏輯控制拆又。
實(shí)現(xiàn)防抖效果(useSumRef.ts
):
import {customRef } from "vue";
export default function(initValue:string,delay:number){
let msg = customRef((track,trigger)=>{
let timer:number
return {
get(){
track() // 告訴Vue數(shù)據(jù)msg很重要儒旬,要對msg持續(xù)關(guān)注,一旦變化就更新
return initValue
},
set(value){
clearTimeout(timer)
timer = setTimeout(() => {
initValue = value
trigger() //通知Vue數(shù)據(jù)msg變化了
}, delay);
}
}
})
return {msg}
}
組件中使用:
8. Vue3新組件
8.1. 【Teleport】
- 什么是Teleport帖族?—— Teleport 是一種能夠?qū)⑽覀兊?strong>組件html結(jié)構(gòu)移動到指定位置的技術(shù)栈源。
<teleport to='body' >
<div class="modal" v-show="isShow">
<h2>我是一個彈窗</h2>
<p>我是彈窗中的一些內(nèi)容</p>
<button @click="isShow = false">關(guān)閉彈窗</button>
</div>
</teleport>
8.2. 【Suspense】
- 等待異步組件時渲染一些額外內(nèi)容,讓應(yīng)用有更好的用戶體驗(yàn)
- 使用步驟:
- 異步引入組件
- 使用
Suspense
包裹組件竖般,并配置好default
與fallback
import { defineAsyncComponent,Suspense } from "vue";
const Child = defineAsyncComponent(()=>import('./Child.vue'))
<template>
<div class="app">
<h3>我是App組件</h3>
<Suspense>
<template v-slot:default>
<Child/>
</template>
<template v-slot:fallback>
<h3>加載中.......</h3>
</template>
</Suspense>
</div>
</template>
8.3.【全局API轉(zhuǎn)移到應(yīng)用對象】
app.component
app.config
app.directive
app.mount
app.unmount
app.use
8.4.【其他】
- 過渡類名
v-enter
修改為v-enter-from
甚垦、過渡類名v-leave
修改為v-leave-from
。
keyCode
作為v-on
修飾符的支持。v-model
指令在組件上的使用已經(jīng)被重新設(shè)計艰亮,替換掉了v-bind.sync闭翩。
v-if
和v-for
在同一個元素身上使用時的優(yōu)先級發(fā)生了變化。移除了
$on
迄埃、$off
和$once
實(shí)例方法男杈。移除了過濾器
filter
。-
移除了
$children
實(shí)例propert
调俘。......