1. Vue3簡介
-
2020年9月18日,
Vue.js
釋出版3.0
版本,代號:One Piece
(n -
經歷了:4800+次提交、40+個RFC、600+次PR、300+貢獻者
-
官方發版地址:Release v3.0.0 One Piece · vuejs/core
-
截止2023年10月,最新的公開版本為:
3.3.4
1.1. 【效能的提升】
-
打包大小減少
41%
。 -
初次渲染快
55%
, 更新渲染快133%
。 -
記憶體減少
54%
。
1.2.【 原始碼的升級】
-
使用
Proxy
代替defineProperty
實現響應式。 -
重寫虛擬
DOM
的實現和Tree-Shaking
。
1.3. 【擁抱TypeScript】
Vue3
可以更好的支援TypeScript
。
1.4. 【新的特性】
-
Composition API
(組合API
):-
setup
-
ref
與reactive
-
computed
與watch
......
-
-
新的內建元件:
-
Fragment
-
Teleport
-
Suspense
......
-
-
其他改變:
-
新的生命週期鉤子
-
data
選項應始終被宣告為一個函式 -
移除
keyCode
支援作為v-on
的修飾符......
-
2. 建立Vue3工程
2.1. 【基於 vue-cli 建立】
點選檢視官方文件
備註:目前
vue-cli
已處於維護模式,官方推薦基於Vite
建立專案。
## 檢視@vue/cli版本,確保@vue/cli版本在4.5.0以上
vue --version
## 安裝或者升級你的@vue/cli
npm install -g @vue/cli
## 執行建立命令
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 建立】(推薦)
vite
是新一代前端構建工具,官網地址:https://vitejs.cn,vite
的優勢如下:
- 輕量快速的熱過載(
HMR
),能實現極速的服務啟動。 - 對
TypeScript
、JSX
、CSS
等支援開箱即用。 - 真正的按需編譯,不再等待整個應用編譯完成。
webpack
構建 與vite
構建對比圖如下:
- 具體操作如下(點選檢視官方文件)
## 1.建立命令
npm create vue@latest
## 2.具體配置
## 配置專案名稱
√ Project name: vue3_test
## 是否新增TypeScript支援
√ Add TypeScript? Yes
## 是否新增JSX支援
√ Add JSX Support? No
## 是否新增路由環境
√ Add Vue Router for Single Page Application development? No
## 是否新增pinia環境
√ 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
外掛:
總結:
Vite
專案中,index.html
是專案的入口檔案,在專案最外層。- 載入
index.html
後,Vite
解析<script type="module" src="xxx">
指向的JavaScript
。 Vue3
**中是透過 **createApp
函式建立一個應用例項。
2.3. 【一個簡單的效果】
Vue3
向下相容Vue2
語法,且Vue3
中的模板中可以沒有根標籤
<template>
<div class="person">
<h2>姓名:{{name}}</h2>
<h2>年齡:{{age}}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">年齡+1</button>
<button @click="showTel">點我檢視聯絡方式</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
設計是Options
(配置)風格的。Vue3
的API
設計是Composition
(組合)風格的。
Options API 的弊端
Options
型別的 API
,資料、方法、計算屬性等,是分散在:data
、methods
、computed
中的,若想新增或者修改一個需求,就需要分別修改:data
、methods
、computed
,不便於維護和複用。
Composition API 的優勢
可以用函式的方式,更加優雅的組織程式碼,讓相關功能的程式碼更加有序的組織在一起。
說明:以上四張動圖原創作者:大帥老猿
3.2. 【拉開序幕的 setup】
setup 概述
setup
是Vue3
中一個新的配置項,值是一個函式,它是 Composition API
“表演的舞臺”,元件中所用到的:資料、方法、計算屬性、監視......等等,均配置在setup
中。
特點如下:
setup
函式返回的物件中的內容,可直接在模板中使用。setup
中訪問this
是undefined
。setup
函式會在beforeCreate
之前呼叫,它是“領先”所有鉤子執行的。
<template>
<div class="person">
<h2>姓名:{{name}}</h2>
<h2>年齡:{{age}}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">年齡+1</button>
<button @click="showTel">點我檢視聯絡方式</button>
</div>
</template>
<script lang="ts">
export default {
name:'Person',
setup(){
// 資料,原來寫在data中(注意:此時的name、age、tel資料都不是響應式資料)
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)
}
// 返回一個物件,物件中的內容,模板中可以直接使用
return {name,age,tel,changeName,changeAge,showTel}
}
}
</script>
setup 的返回值
- 若返回一個物件:則物件中的:屬性、方法等,在模板中均可以直接使用(重點關注)。
- 若返回一個函式:則可以自定義渲染內容,程式碼如下:
setup(){
return ()=> '你好啊!'
}
setup 與 Options API 的關係
Vue2
的配置(data
、methos
......)中可以訪問到setup
中的屬性、方法。- 但在
setup
中不能訪問到Vue2
的配置(data
、methos
......)。 - 如果與
Vue2
衝突,則setup
優先。
setup 語法糖
setup
函式有一個語法糖,這個語法糖,可以讓我們把setup
獨立出去,程式碼如下:
<template>
<div class="person">
<h2>姓名:{{name}}</h2>
<h2>年齡:{{age}}</h2>
<button @click="changName">修改名字</button>
<button @click="changAge">年齡+1</button>
<button @click="showTel">點我檢視聯絡方式</button>
</div>
</template>
<script lang="ts">
export default {
name:'Person',
}
</script>
<!-- 下面的寫法是setup語法糖 -->
<script setup lang="ts">
console.log(this) //undefined
// 資料(注意:此時的name、age、tel都不是響應式資料)
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>
這樣寫了之後,之前的script一般就拿來寫個name就可以了,但是隻是寫個name專門用個script標籤麻煩,所以可以藉助下面的方法
直接在steup的script標籤寫上
擴充套件:上述程式碼,還需要編寫一個不寫setup
的script
標籤,去指定元件名字,比較麻煩,我們可以藉助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 建立:基本型別的響應式資料】
- 作用:定義響應式變數。
- 語法:
let xxx = ref(初始值)
。 - 返回值:一個
RefImpl
的例項物件,簡稱ref物件
或ref
,ref
物件的value
屬性是響應式的。
加了ref後就是ref的一個例項物件,然後值是在裡面的value屬性裡面
-
注意點:
JS
中運算元據需要:xxx.value
,但模板中不需要.value
,直接使用即可。(本來是要name.value,模板裡面自己給你新增上了value就不用新增)- 對於
let name = ref('張三')
來說,name
不是響應式的,name.value
是響應式的。但是要修改他的話,在修改(函式)裡面是需要寫明value的才能響應式
<template>
<div class="person">
<h2>姓名:{{name}}</h2>
<h2>年齡:{{age}}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">年齡+1</button>
<button @click="showTel">點我檢視聯絡方式</button>
</div>
</template>
<script setup lang="ts" name="Person">
import {ref} from 'vue'
// name和age是一個RefImpl的例項物件,簡稱ref物件,它們的value屬性是響應式的。
let name = ref('張三')
let age = ref(18)
// tel就是一個普通的字串,不是響應式的
let tel = '13888888888'
function changeName(){
// JS中操作ref物件時候需要.value
name.value = '李四'
console.log(name.value)
// 注意:name不是響應式的,name.value是響應式的,所以如下程式碼並不會引起頁面的更新。
// name = ref('zhang-san')
}
function changeAge(){
// JS中操作ref物件時候需要.value
age.value += 1
console.log(age.value)
}
function showTel(){
alert(tel)
}
</script>
3.4. 【reactive 建立:物件型別的響應式資料】
- 作用:定義一個響應式物件(基本型別不要用它,要用
ref
,否則報錯) - 語法:
let 響應式物件= reactive(源物件)
。 - 返回值:一個
Proxy
的例項物件,簡稱:響應式物件。
這個是將一個物件程式設計一個proxy建立的物件接受響應式(陣列同理也是這個方法)
- 注意點:
reactive
定義的響應式資料是“深層次”的。
<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'
// 資料
let car = reactive({ brand: '賓士', price: 100 })
let games = reactive([
{ id: 'ahsgdyfa01', name: '英雄聯盟' },
{ 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 建立:物件型別的響應式資料】
- 其實
ref
接收的資料可以是:基本型別、物件型別。ref可以對物件響應式資料,但是注意改的時候要加value,特別是陣列形式加在哪裡
其原理還是把變數做了一個響應式變成了value形式,但是裡面的物件還是做了一個proxy
- 若
ref
接收的是物件型別,內部其實也是呼叫了reactive
函式。
<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'
// 資料
let car = ref({ brand: '賓士', price: 100 })
let games = ref([
{ id: 'ahsgdyfa01', name: '英雄聯盟' },
{ 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
用來定義:基本型別資料、物件型別資料;
reactive
用來定義:物件型別資料。
- 區別:
ref
建立的變數必須使用.value
(可以使用volar
外掛自動新增.value
)。
reactive
重新分配一個新物件,會失去響應式(可以使用Object.assign
去整體替換)。這樣寫也不行
可以這樣
如果是用的ref響應物件,就可以直接改
- 使用原則:
- 若需要一個基本型別的響應式資料,必須使用
ref
。- 若需要一個響應式物件,層級不深,
ref
、reactive
都可以。- 若需要一個響應式物件,且層級較深,推薦使用
reactive
。
3.7. 【toRefs 與 toRef】
如果像這樣解構賦值,那麼出來的name age都不是響應式資料
想讓響應式資料轉換出來也是響應式就押用到這個
- 作用:將一個響應式物件中的每一個屬性,轉換為
ref
物件。
注意:torefs之後會建立一個新的reactive響應式物件,但是值都是指向同一個值你修改一個兩個都會變
- 備註:
toRefs
與toRef
功能一致,但toRefs
可以批次轉換。 - 語法如下:
<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'
// 資料
let person = reactive({name:'張三', age:18, gender:'男'})
// 透過toRefs將person物件中的n個屬性批次取出,且依然保持響應式的能力
let {name,gender} = toRefs(person)
// 透過toRef將person物件中的gender屬性取出,且依然保持響應式的能力
let age = toRef(person,'age')
// 方法
function changeName(){
name.value += '~'
}
function changeAge(){
age.value += 1
}
function changeGender(){
gender.value = '女'
}
</script>
3.8. 【computed】
作用:根據已有資料計算出新資料(和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】
- 作用:監視資料的變化(和
Vue2
中的watch
作用一致) - 特點:
Vue3
中的watch
只能監視以下四種資料:
ref
定義的資料。reactive
定義的資料。- 函式返回一個值(
getter
函式)。- 一個包含上述內容的陣列。
我們在Vue3
中使用watch
的時候,通常會遇到以下幾種情況:
* 情況一
監視ref
定義的【基本型別】資料:直接寫資料名即可,監視的是其value
值的改變。
stopwatch監視會返回一個值是一個箭頭函式,呼叫這個函式可以取消掉監視
<template>
<div class="person">
<h1>情況一:監視【ref】定義的【基本型別】資料</h1>
<h2>當前求和為:{{sum}}</h2>
<button @click="changeSum">點我sum+1</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {ref,watch} from 'vue'
// 資料
let sum = ref(0)
// 方法
function changeSum(){
sum.value += 1
}
// 監視,情況一:監視【ref】定義的【基本型別】資料
const stopWatch = watch(sum,(newValue,oldValue)=>{
console.log('sum變化了',newValue,oldValue)
if(newValue >= 10){
stopWatch()
}
})
</script>
* 情況二
監視ref
定義的【物件型別】資料:直接寫資料名,監視的是物件的【地址值】,若想監視物件內部的資料,要手動開啟深度監視。
注意:
- 若修改的是
ref
定義的物件中的屬性,newValue
和oldValue
都是新值,因為它們是同一個物件。
- 若修改整個
ref
定義的物件,newValue
是新值,oldValue
是舊值,因為不是同一個物件了。不開啟深度,整個物件改變了,才會觸發監視
<template>
<div class="person">
<h1>情況二:監視【ref】定義的【物件型別】資料</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'
// 資料
let person = ref({
name:'張三',
age:18
})
// 方法
function changeName(){
person.value.name += '~'
}
function changeAge(){
person.value.age += 1
}
function changePerson(){
person.value = {name:'李四',age:90}
}
/*
監視,情況一:監視【ref】定義的【物件型別】資料,監視的是物件的地址值,若想監視物件內部屬性的變化,需要手動開啟深度監視
watch的第一個引數是:被監視的資料
watch的第二個引數是:監視的回撥
watch的第三個引數是:配置物件(deep、immediate等等.....)
*/
watch(person,(newValue,oldValue)=>{
console.log('person變化了',newValue,oldValue)
},{deep:true})
</script>
* 情況三
監視reactive
定義的【物件型別】資料,且預設開啟了深度監視。並且修改屬性新老value還是一樣的
<template>
<div class="person">
<h1>情況三:監視【reactive】定義的【物件型別】資料</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'
// 資料
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
}
// 監視,情況三:監視【reactive】定義的【物件型別】資料,且預設是開啟深度監視的
watch(person,(newValue,oldValue)=>{
console.log('person變化了',newValue,oldValue)
})
watch(obj,(newValue,oldValue)=>{
console.log('Obj變化了',newValue,oldValue)
})
</script>
* 情況四
監視ref
或reactive
定義的【物件型別】資料中的某個屬性,注意點如下:
-
若該屬性值不是【物件型別】,需要寫成函式形式。
-
若該屬性值是依然是【物件型別】,可直接寫,也可寫成函式,建議寫成函式。
但是此時如果是這整個物件改變不會監視到
這樣就是這個物件怎麼變都監視得到,寫成函式是為了整個物件變,deep是物件裡面變化
結論:監視的要是物件裡的屬性,那麼最好寫函式式,注意點:若是物件監視的是地址值,需要關注物件內部,需要手動開啟深度監視。
<template>
<div class="person">
<h1>情況四:監視【ref】或【reactive】定義的【物件型別】資料中的某個屬性</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'
// 資料
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:'愛瑪'}
}
// 監視,情況四:監視響應式物件中的某個屬性,且該屬性是基本型別的,要寫成函式式
/* watch(()=> person.name,(newValue,oldValue)=>{
console.log('person.name變化了',newValue,oldValue)
}) */
// 監視,情況四:監視響應式物件中的某個屬性,且該屬性是物件型別的,可以直接寫,也能寫函式,更推薦寫函式
watch(()=>person.car,(newValue,oldValue)=>{
console.log('person.car變化了',newValue,oldValue)
},{deep:true})
</script>
* 情況五
監視上述的多個資料
寫成陣列形式就可以,value是這個陣列
<template>
<div class="person">
<h1>情況五:監視上述的多個資料</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'
// 資料
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:'愛瑪'}
}
// 監視,情況五:監視上述的多個資料
watch([()=>person.name,person.car],(newValue,oldValue)=>{
console.log('person.car變化了',newValue,oldValue)
},{deep:true})
</script>
3.10. 【watchEffect】
-
官網:立即執行一個函式,同時響應式地追蹤其依賴,並在依賴更改時重新執行該函式。
-
watch
對比watchEffect
-
都能監聽響應式資料的變化,不同的是監聽資料變化的方式不同
-
watch
:要明確指出監視的資料 -
watchEffect
:不用明確指出監視的資料(函式中用到哪些屬性,那就監視哪些屬性)。
-
比如這個例子,都可以完成但是watcheffect上來就執行一次,而且會根據裡面所寫的自動監測要監視誰
-
示例程式碼:
<template> <div class="person"> <h1>需求:水溫達到50℃,或水位達到20cm,則聯絡伺服器</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' // 資料 let temp = ref(0) let height = ref(0) // 方法 function changePrice(){ temp.value += 10 } function changeSum(){ height.value += 1 } // 用watch實現,需要明確的指出要監視:temp、height watch([temp,height],(value)=>{ // 從value中獲取最新的temp值、height值 const [newTemp,newHeight] = value // 室溫達到50℃,或水位達到20cm,立刻聯絡伺服器 if(newTemp >= 50 || newHeight >= 20){ console.log('聯絡伺服器') } }) // 用watchEffect實現,不用 const stopWtach = watchEffect(()=>{ // 室溫達到50℃,或水位達到20cm,立刻聯絡伺服器 if(temp.value >= 50 || height.value >= 20){ console.log(document.getElementById('demo')?.innerText) console.log('聯絡伺服器') } // 水溫達到100,或水位達到50,取消監視 if(temp.value === 100 || height.value === 50){ console.log('清理了') stopWtach() } }) </script>
3.11. 【標籤的 ref 屬性】
作用:用於註冊模板引用。
用在普通
DOM
標籤上,獲取的是DOM
節點。用在元件標籤上,獲取的是元件例項物件。
用在普通DOM
標籤上:
<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">點我列印內容</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')
// 列印內容
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>
用在元件標籤上:
但是要獲取元件上的資料需要子元件做出defineexpose匯出操作
<!-- 父元件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暴露內容 -->
<script lang="ts" setup name="Person">
import {ref,defineExpose} from 'vue'
// 資料
let name = ref('張三')
let age = ref(18)
/****************************/
/****************************/
// 使用defineExpose將元件中的資料交給外部
defineExpose({name,age})
</script>
3.11.1 回顧TS中的介面、泛型、自定義型別
注意vue3引用元件直接匯入即可
如果在子元件定義了一組資料,但是不小心名字打錯了
後期就容易呼叫不到
我們就可以去定義一個介面來約束型別,一般是在src下面建立一個types資料夾,然後匯出
要用的話就在元件匯入,並且前面要加type表明匯入的是一個型別,然後在要用的變數後規則如下
如果是一個陣列資料想這樣用就不行
除非是用泛型先規定它是一個陣列,然後陣列裡面每一項需要符合這個規則
當然也可以去自定義型別,直接在ts裡面宣告
3.12. 【props】
// 定義一個介面,限制每個Person物件的格式 export interface PersonInter { id:string, name:string, age:number } // 定義一個自定義型別Persons export type Persons = Array<PersonInter>
App.vue
中程式碼:注意用reactive響應式資料,此時介面應該用泛型來約束,而且資料是不能多不能少的
如果某個屬性想可有可無
<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
中程式碼:只接受會出問題,萬一傳過來是個數字也會遍歷
注意接收+限制型別的寫法,prop括號裡面不寫了,提出來規定一個泛型,裡面為一個物件,誰要約束什麼
限制必要性,一個?父元件就可傳可不傳了
如果不寫自己還有預設值,匯入,再用
用的時候注意,把原來寫的全部包起來,第二個引數寫預設值,並且鍵值對,屬性值不能為一個陣列形式,可以寫為一個函式返回
<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}>() // 第三種寫法:接收+限制型別+指定預設值+限制必要性 let props = withDefaults(defineProps<{list?:Persons}>(),{ list:()=>[{id:'asdasg01',name:'小豬佩奇',age:18}] }) console.log(props) </script>
3.13. 【生命週期】
-
規律:
生命週期整體分為四個階段,分別是:建立、掛載、更新、銷燬,每個階段都有兩個鉤子,一前一後。
-
Vue2
的生命週期建立階段:
beforeCreate
、created
掛載階段:
beforeMount
、mounted
更新階段:
beforeUpdate
、updated
銷燬階段:
beforeDestroy
、destroyed
v3的建立就是setup本身
-
Vue3
的生命週期建立階段:
setup
掛載階段:
onBeforeMount
、onMounted
更新階段:
onBeforeUpdate
、onUpdated
解除安裝階段:
onBeforeUnmount
、onUnmounted
-
常用的鉤子:
onMounted
(掛載完畢)、onUpdated
(更新完畢)、onBeforeUnmount
(解除安裝之前) -
示例程式碼:
<template> <div class="person"> <h2>當前求和為:{{ sum }}</h2> <button @click="changeSum">點我sum+1</button> </div> </template> <!-- vue3寫法 --> <script lang="ts" setup name="Person"> import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue' // 資料 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】
如果一個頁面中全是資料和方法,看起很臃腫複雜,跟vue2又很像
注意:如果要往空陣列push等作出操作需要規定陣列的泛型
而hooks就類似於mixin,把所有程式碼模組化,src建立資料夾hooks,建立模組的ts檔案,把該屬於這個部分的程式碼都放進來
注意:這裡面就跟剛才一樣什麼都能寫包括生命鉤子,但是會對外暴露一個函式,然後把所有資料方法返回
元件要用就直接匯入,並且那邊是函式,這邊直接呼叫,透過解構賦值,獲得return出來的資料和方法
示例程式碼:
-
useSum.ts
中內容如下:import {ref,onMounted} from 'vue' export default function(){ let sum = ref(0) const increment = ()=>{ sum.value += 1 } const decrement = ()=>{ sum.value -= 1 } onMounted(()=>{ increment() }) //向外部暴露資料 return {sum,increment,decrement} }
-
useDog.ts
中內容如下:import {reactive,onMounted} from 'vue' import axios,{AxiosError} from 'axios' export default function(){ let dogList = reactive<string[]>([]) // 方法 async function getDog(){ try { // 發請求 let {data} = await axios.get('https://dog.ceo/api/breed/pembroke/images/random') // 維護資料 dogList.push(data.message) } catch (error) { // 處理錯誤 const err = <AxiosError>error console.log(err.message) } } // 掛載鉤子 onMounted(()=>{ getDog() }) //向外部暴露資料 return {dogList,getDog} }
-
元件中具體使用:
<template> <h2>當前求和為:{{sum}}</h2> <button @click="increment">點我+1</button> <button @click="decrement">點我-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
版本。 -
基本規則
第一步,建立導航區,展示區
- 第二步建立store資料夾,匯入兩個api,第一個必須宣告路由工作模式,第二個如何來建立路由createrouter
入口檔案匯入註冊
-
路由配置檔案程式碼如下:
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> <!-- 導航區 --> <div class="navigate"> <RouterLink to="/home" active-class="active">首頁</RouterLink> <RouterLink to="/news" active-class="active">新聞</RouterLink> <RouterLink to="/about" active-class="active">關於</RouterLink> </div> <!-- 展示區 --> <div class="main-content"> <RouterView></RouterView> </div> </div> </template> <script lang="ts" setup name="App"> import {RouterLink,RouterView} from 'vue-router' </script>
4.3. 【兩個注意點】
路由元件通常存放在
pages
或views
資料夾,一般元件通常存放在components
資料夾。透過點選導航,視覺效果上“消失” 了的路由元件,預設是被解除安裝掉的,需要的時候再去掛載。
4.4.【路由器工作模式】
-
history
模式優點:
URL
更加美觀,不帶有#
,更接近傳統的網站URL
。缺點:後期專案上線,需要服務端配合處理路徑問題,否則重新整理會有
404
錯誤。const router = createRouter({ history:createWebHistory(), //history模式 /******/ })
-
hash
模式優點:相容性更好,因為不需要伺服器端處理路徑。
缺點:
URL
帶有#
不太美觀,且在SEO
最佳化方面相對較差。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. 【命名路由】
作用:可以簡化路由跳轉及傳參(後面就講)。
給路由規則命名:
routes:[
{
name:'zhuye',
path:'/home',
component:Home
},
{
name:'xinwen',
path:'/news',
component:News,
},
{
name:'guanyu',
path:'/about',
component:About
}
]
跳轉路由:
<!--簡化前:需要寫完整的路徑(to的字串寫法) -->
<router-link to="/news/detail">跳轉</router-link>
<!--簡化後:直接透過名字跳轉(to的物件寫法配合name屬性) -->
<router-link :to="{name:'guanyu'}">跳轉</router-link>
4.7. 【巢狀路由】
-
編寫
News
的子路由:Detail.vue
-
配置路由規則,使用
children
配置項: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
-
跳轉路由(記得要加完整路徑):
<router-link to="/news/detail">xxxx</router-link> <!-- 或 --> <router-link :to="{path:'/news/detail'}">xxxx</router-link>
-
記得去
Home
元件中預留一個<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引數
-
傳遞引數
<!-- 跳轉並攜帶query引數(to的字串寫法) --> <router-link to="/news/detail?a=1&b=2&content=歡迎你"> 跳轉 </router-link> <!-- 跳轉並攜帶query引數(to的物件寫法) --> <RouterLink :to="{ //name:'xiang', //用name也可以跳轉 path:'/news/detail', query:{ id:news.id, title:news.title, content:news.content } }" > {{news.title}} </RouterLink>
-
接收引數:
import {useRoute} from 'vue-router' const route = useRoute() // 列印query引數 console.log(route.query)
接收引數解構賦值版
但是這樣有個問題,之前說過在響應式物件上面直接解構,會讓他失去響應式,解決方法是一個torefs
params引數
要配置
-
傳遞引數
<!-- 跳轉並攜帶params引數(to的字串寫法) --> <RouterLink :to="`/news/detail/001/新聞001/內容001`">{{news.title}}</RouterLink> <!-- 跳轉並攜帶params引數(to的物件寫法) --> <RouterLink :to="{ name:'xiang', //用name跳轉 params:{ id:news.id, title:news.title, content:news.title } }" > {{news.title}} </RouterLink>
-
接收引數:
import {useRoute} from 'vue-router' const route = useRoute() // 列印params引數 console.log(route.params)
備註1:傳遞
params
引數時,若使用to
的物件寫法,必須使用name
配置項,不能用path
。備註2:傳遞
params
引數時,需要提前在規則中佔位。
4.9. 【路由的props配置】
就算剛才用這個方法,但是還是複雜在標籤上
作用:讓路由元件更方便的收到引數(可以將路由引數作為props
傳給元件)
第一種,只需要在路由配置裡面開啟
第二種
{
name:'xiang',
path:'detail/:id/:title/:content',
component:Detail,
// props的物件寫法,作用:把物件中的每一組key-value作為props傳給Detail元件
// props:{a:1,b:2,c:3},
// props的布林值寫法,作用:把收到了每一組params引數,作為props傳給Detail元件
// props:true
// props的函式寫法,作用:把返回的物件中每一組key-value作為props傳給Detail元件
props(route){
return route.query
}
}
4.10. 【 replace屬性】
-
作用:控制路由跳轉時操作瀏覽器歷史記錄的模式。
-
瀏覽器的歷史記錄有兩種寫入方式:分別為
push
和replace
:push
是追加歷史記錄(預設值)。replace
是替換當前記錄。
-
開啟
replace
模式:<RouterLink replace .......>News</RouterLink>
4.11. 【程式設計式導航】
路由元件的兩個重要的屬性:$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【準備一個效果】
點選獲取,就往陣列增加一條土味情話
注意先從promise返回解構data,如果還想繼續從data裡面解構可以在後面來個:加物件,因為content是data物件裡面的,甚至直接給content:給他來個別名
5.2【搭建 pinia 環境】
第一步:npm install pinia
第二步:操作src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
/* 引入createPinia,用於建立pinia */
import { createPinia } from 'pinia'
/* 建立pinia */
const pinia = createPinia()
const app = createApp(App)
/* 使用外掛 */{}
app.use(pinia)
app.mount('#app')
此時開發者工具中已經有了pinia
選項
5.3【儲存+讀取資料】
-
它有三個概念:
state
、getter
、action
,相當於元件中的:data
、computed
和methods
。 -
具體編碼:
src/store/count.ts
取名最好要跟hooks一樣, 函式里面的名字最好跟檔名保持一致
// 引入defineStore用於建立store import {defineStore} from 'pinia' // 定義並暴露一個store export const useCountStore = defineStore('count',{ // 動作 actions:{}, // 狀態 state(){ return { sum:6 } }, // 計算 getters:{} })
-
具體編碼:
src/store/talk.ts
// 引入defineStore用於建立store import {defineStore} from 'pinia' // 定義並暴露一個store export const useTalkStore = defineStore('talk',{ // 動作 actions:{}, // 狀態 state(){ return { talkList:[ {id:'yuysada01',content:'你今天有點怪,哪裡怪?怪好看的!'}, {id:'yuysada02',content:'草莓、藍莓、蔓越莓,你想我了沒?'}, {id:'yuysada03',content:'心裡給你留了一塊地,我的死心塌地'} ] } }, // 計算 getters:{} })
-
元件中使用
state
中的資料注意:單獨用ref那麼是.value,但是如果裡面是ref定義,外面又用了一個reactive,那他就會給你拆包,也就是直接拿不用再value
得到對應store之後它是一個reactive物件,然後裡面state定義的資料是放在ref裡面的,那就拆包了
<template> <h2>當前求和為:{{ sumStore.sum }}</h2> </template> <script setup lang="ts" name="Count"> // 引入對應的useXxxxxStore import {useSumStore} from '@/store/sum' // 呼叫useXxxxxStore得到對應的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.【修改資料】(三種方式)
-
第一種修改方式,直接修改
countStore.sum = 666
-
第二種修改方式:批次修改
要改的資料較多的時候
countStore.$patch({ sum:999, school:'atguigu' })
-
第三種修改方式:藉助
action
修改(action
中可以編寫一些業務邏輯)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 } } }, /*************/ })
-
元件中呼叫
action
即可// 使用countStore const countStore = useCountStore() // 呼叫對應action countStore.incrementOdd(n.value)
5.5.【storeToRefs】
有個問題,現在模板上全部要加什麼.sum都有個字首,所以我想就是直接解構賦值拿到我要的資料,但是從響應式解構賦值又是老問題了
這樣做可以得到響應式資料,但有點浪費,因為他不僅是將資料變成ref,把整個store包括裡面的方法也變了
所以就可以用pinia上有個專門的方法來轉他就是隻變資料
- 藉助
storeToRefs
將store
中的資料轉為ref
物件,方便在模板中使用。 - 注意:
pinia
提供的storeToRefs
只會將資料做轉換,而Vue
的toRefs
會轉換store
中資料。
<template>
<div class="count">
<h2>當前求和為:{{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轉換countStore,隨後解構 */
const {sum} = storeToRefs(countStore)
</script>
5.6.【getters】
-
概念:當
state
中的資料,需要經過處理後再使用時,可以使用getters
配置。 -
追加
getters
配置。// 引入defineStore用於建立store import {defineStore} from 'pinia' // 定義並暴露一個store export const useCountStore = defineStore('count',{ // 動作 actions:{ /************/ }, // 狀態 state(){ return { sum:1, school:'atguigu' } }, // 計算 getters:{ bigSum:(state):number => state.sum *10, upperSchool():string{ return this. school.toUpperCase() } } })
-
元件中讀取資料:
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函式相當於action
async function getATalk(){
// 發請求,下面這行的寫法是:連續解構賦值+重新命名
let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
// 把請求回來的字串,包裝成一個物件
let obj = {id:nanoid(),title}
// 放到陣列中
talkList.unshift(obj)
}
return {talkList,getATalk}
})
6. 元件通訊
Vue3
元件通訊和Vue2
的區別:
- 移出事件匯流排,使用
mitt
代替。
vuex
換成了pinia
。- 把
.sync
最佳化到了v-model
裡面了。 - 把
$listeners
所有的東西,合併到$attrs
中了。 $children
被砍掉了。
常見搭配形式:
6.1. 【props】
概述:props
是使用頻率最高的一種通訊方式,常用與 :父 ↔ 子。
- 若 父傳子:屬性值是非函式。
- 若 子傳父:屬性值是函式。
父元件:
<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";
// 資料
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. 【自定義事件】
- 概述:自定義事件常用於:子 => 父。
event型別可以為event
- 注意區分好:原生事件、自定義事件。
- 原生事件:
- 事件名是特定的(
click
、mosueenter
等等) - 事件物件
$event
: 是包含事件相關資訊的物件(pageX
、pageY
、target
、keyCode
)
- 事件名是特定的(
- 自定義事件:
- 事件名是任意名稱
- 事件物件
$event
: 是呼叫emit
時所提供的資料,可以是任意型別!!!
-
示例:
<!--在父元件中,給子元件繫結自定義事件:--> <Child @send-toy="toy = $event"/> <!--注意區分原生事件與自定義事件中的$event--> <button @click="toy = $event">測試</button>
//子元件中,觸發事件: this.$emit('send-toy', 具體資料)
也可以正常走邏輯,這樣來傳
6.3. 【mitt】
概述:與訊息訂閱與釋出(pubsub
)功能類似,可以實現任意元件間通訊。
安裝mitt
npm i mitt
新建檔案:src\utils\emitter.ts
都是寫在這個ts檔案裡面,一共四個api,on是繫結,off解綁,emit觸發,all全部事件可以.clear全部清空
// 引入mitt
import mitt from "mitt";
// 建立emitter
const emitter = mitt()
/*
// 繫結事件
emitter.on('abc',(value)=>{
console.log('abc事件被觸發',value)
})
emitter.on('xyz',(value)=>{
console.log('xyz事件被觸發',value)
})
setInterval(() => {
// 觸發事件
emitter.emit('abc',666)
emitter.emit('xyz',777)
}, 1000);
setTimeout(() => {
// 清理事件
emitter.all.clear()
}, 3000);
*/
// 建立並暴露mitt
export default emitter
元件內使用
接收資料的元件中:繫結事件、同時在銷燬前解綁事件:
import emitter from "@/utils/emitter";
import { onUnmounted } from "vue";
// 繫結事件
emitter.on('send-toy',(value)=>{
console.log('send-toy事件被觸發',value)
})
onUnmounted(()=>{
// 解綁事件
emitter.off('send-toy')
})
【第三步】:提供資料的元件,在合適的時候觸發事件
import emitter from "@/utils/emitter";
function sendToy(){
// 觸發事件
emitter.emit('send-toy',toy.value)
}
注意這個重要的內建關係,匯流排依賴著這個內建關係
6.4.【v-model】
-
概述:實現 父↔子 之間相互通訊。
-
前序知識 ——
v-model
的本質直接寫$event.target.value會報錯,e.target是一個物件,這個獨享就可能為空,所以這裡用了個斷言告訴他是一個html輸入的元素
<!-- 使用v-model指令 --> <input type="text" v-model="userName"> <!-- v-model的本質是下面這行程式碼 --> <input type="text" :value="userName" @input="userName =(<HTMLInputElement>$event.target).value" >
-
元件標籤上的
v-model
的本質::moldeValue
+update:modelValue
事件。下面是本質,上面是程式設計師簡寫方式,如果v-model不寫別名,預設名字傳過去就是modelValue
<!-- 元件標籤上使用v-model指令 --> <AtguiguInput v-model="userName"/> <!-- 元件標籤上v-model的本質 --> <AtguiguInput :modelValue="userName" @update:model-value="userName = $event"/>
AtguiguInput
元件中:<template> <div class="box"> <!--將接收的value值賦給input元素的value屬性,目的是:為了呈現資料 --> <!--給input元素繫結原生input事件,觸發input事件時,進而觸發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
也可以改名字,注意:使用父元件這邊可以直接使用簡寫方式,但是接受子元件這邊,還是要跟著原理來,一個props繫結,一個觸發自定義事件
<!-- 也可以更換value,例如改成abc--> <AtguiguInput v-model:abc="userName"/> <!-- 上面程式碼的本質如下 --> <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
可以更換,那麼就可以在元件標籤上多次使用v-model
<AtguiguInput v-model:abc="userName" v-model:xyz="password"/>
6.5.【$attrs 】
-
概述:(祖→孫)。
-
具體說明:
$attrs
是一個物件,包含所有父元件傳入的標籤屬性。注意:
$attrs
會自動排除props
中宣告的屬性(可以認為宣告過的props
被子元件自己“消費”了)
父元件:
<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>
孫元件:
v-bind後面寫物件,相當於一個一個繫結
<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)">點我更新A</button>
</div>
</template>
<script setup lang="ts" name="GrandChild">
defineProps(['a','b','c','d','x','y','updateA'])
</script>
父傳一個自定義事件,同理也可以實現孫給祖傳
6.6. 【$refs、
$parent】
-
概述:
$refs
用於 :父→子。
父元件這邊先給子元件繫結ref,然後給這個ref的名字呼叫ref函式,透過事件就可以拿到子元件傳過來的
子元件
一個宏函式把資料曝光出來
`$refs就是一個api獲取所有子元件
這裡面放的就是一個個打了ref的子元件物件
$parent
用於:子→父。
父元件
-
原理如下:
屬性 說明 $refs
值為物件,包含所有被 ref
屬性標識的DOM
元素或元件例項。$parent
值為物件,當前元件的父元件例項物件。
6.7. 【provide、inject】
-
概述:實現祖孫元件直接通訊(attrs需要中間人)
-
具體使用:
- 在祖先元件中透過
provide
配置向後代元件提供資料 - 在後代元件中透過
inject
配置來宣告接收資料
- 在祖先元件中透過
-
具體編碼:
【第一步】父元件中,使用
provide
提供資料<template> <div class="father"> <h3>父元件</h3> <h4>資產:{{ money }}</h4> <h4>汽車:{{ car }}</h4> <button @click="money += 1">資產+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"; // 資料 let money = ref(100) let car = reactive({ brand:'賓士', price:100 }) // 用於更新money的方法 function updateMoney(value:number){ money.value += value } // 提供資料 provide('moneyContext',{money,updateMoney}) provide('car',car) </script>
注意:子元件中不用編寫任何東西,是不受到任何打擾的
提供資料時候,ref資料不用.value,前面名字,後面資料
【第二步】孫元件中使用
inject
配置項接受資料。<template> <div class="grand-child"> <h3>我是孫元件</h3> <h4>資產:{{ money }}</h4> <h4>汽車:{{ car }}</h4> <button @click="updateMoney(6)">點我</button> </div> </template> <script setup lang="ts" name="GrandChild"> import { inject } from 'vue'; // 注入資料 let {money,updateMoney} = inject('moneyContext',{money:0,updateMoney:(x:number)=>{}}) let car = inject('car') </script>
注入資料,就用一個變數來接受就可以了,第二個引數是不寫的話也可以由預設值
孫給爺傳
爺元件
孫元件
6.8. 【pinia】
參考之前pinia
部分的講解
6.9. 【slot】
1. 預設插槽
父元件中:
<Category title="今日熱門遊戲">
<ul>
<li v-for="g in games" :key="g.id">{{ g.name }}</li>
</ul>
</Category>
子元件中:
<template>
<div class="item">
<h3>{{ title }}</h3>
<!-- 預設插槽 -->
<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. 作用域插槽
-
理解:資料在元件的自身,但根據資料生成的結構需要元件的使用者來決定。(新聞資料在
News
元件中,但使用資料所遍歷出來的結構由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:'英雄聯盟'}, {id:'asgdytsa02',name:'王者榮耀'}, {id:'asgdytsa03',name:'紅色警戒'}, {id:'asgdytsa04',name:'斗羅大陸'} ]) </script>
作用於插槽也可以有名字
7. 其它 API
7.1.【shallowRef 與 shallowReactive 】
shallowRef
-
作用:建立一個響應式資料,但只對頂層屬性進行響應式處理。
-
用法:
之前用ref繫結的資料隨便改
如果都改為shallowref
之前單資料和整個物件還可以改,但是單獨修改物件裡面某個屬性就不行了
此淺層就是隻把第一層程式設計響應式,.value之後就不管了
用處就是如果有個物件很大,我不關心它裡面某個值變沒變化,就關心物件有沒有整體被改就可以用這個
-
特點:只跟蹤引用值的變化,不關心值內部的屬性變化。
shallowReactive
-
作用:建立一個淺層響應式物件,只會使物件的最頂層屬性變成響應式的,物件內部的巢狀屬性則不會變成響應式的
-
用法:
此時修改都可以生效
如果用淺層次,那就是第二層改不了了
作用同上
-
特點:物件的頂層屬性是響應式的,但巢狀物件的屬性不是。
總結
透過使用
shallowRef()
和shallowReactive()
來繞開深度響應。淺層式API
建立的狀態只在其頂層是響應式的,對所有深層的物件不會做任何處理,避免了對每一個內部屬性做響應式所帶來的效能成本,這使得屬性的訪問變得更快,可提升效能。
7.2.【readonly 與 shallowReadonly】
readonly
-
用法:
只能讀不能改
也可以針對物件,裡面的屬性都不能改
-
特點:
- 物件的所有巢狀屬性都將變為只讀。
- 任何嘗試修改這個物件的操作都會被阻止(在開發模式下,還會在控制檯中發出警告)。
-
應用場景:
- 建立不可變的狀態快照。
- 保護全域性狀態或配置不被修改。
shallowReadonly
-
作用:與
readonly
類似,但只作用於物件的頂層屬性。 -
用法:
只對淺層次生效,也就是第二層可以改裡面的屬性可以改
上面不可改,下面可改
-
特點:
-
只將物件的頂層屬性設定為只讀,物件內部的巢狀屬性仍然是可變的。
-
適用於只需保護物件頂層屬性的場景。
-
7.3.【toRaw 與 markRaw】
toRaw
-
作用:用於獲取一個響應式物件的原始物件,
toRaw
返回的物件不再是響應式的,不會觸發檢視更新。官網描述:這是一個可以用於臨時讀取而不引起代理訪問/跟蹤開銷,或是寫入而不觸發更改的特殊方法。不建議儲存對原始物件的持久引用,請謹慎使用。
何時使用? —— 在需要將響應式物件傳遞給非
Vue
的庫或外部系統時,使用toRaw
可以確保它們收到的是普通物件使用場景,並不推薦,把一個響應物件變為不響應之後給到另一個物件,而是加入一個函式需要這個物件,要動到他,但是也不想引起改變
-
具體編碼:
將一個響應式資料程式設計不再響應
markRaw
-
作用:標記一個物件,使其永遠不會變成響應式的。
例如使用
mockjs
時,為了防止誤把mockjs
變為響應式物件,可以使用markRaw
去標記mockjs
-
編碼:
/* markRaw */ let citys = markRaw([ {id:'asdda01',name:'北京'}, {id:'asdda02',name:'上海'}, {id:'asdda03',name:'天津'}, {id:'asdda04',name:'重慶'} ]) // 根據原始物件citys去建立響應式物件citys2 —— 建立失敗,因為citys被markRaw標記了 let citys2 = reactive(citys)
7.4.【customRef】
作用:建立一個自定義的ref
,並對其依賴項跟蹤和更新觸發進行邏輯控制。
如果使用ref,那就是資料已更新,頁面馬上更新
如果我想實現資料更新,頁面等一下在更新這個做不到
customref基本使用
需要一個初試值,但是模板用的還是ref的
但如果是這樣寫的,頁面根本不會響應
真正寫法,需要在其回撥兩個引數,在讀取時呼叫前面那個函式,修改後呼叫後面那個函式
這兩個函式很重要,面試容易問道
這樣就可以實現剛才的效果了
自定義ref感覺就類似於defineproperty那個函式,就是對資料更新變化的時候可以去寫一些自己的邏輯在裡面
但是此時快速輸入有bug,解決方式如下
實現防抖效果(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資料msg很重要,要對msg持續關注,一旦變化就更新
return initValue
},
set(value){
clearTimeout(timer)
timer = setTimeout(() => {
initValue = value
trigger() //通知Vue資料msg變化了
}, delay);
}
}
})
return {msg}
}
真實公司開發需求可能是要在這裡來一個hooks
8. Vue3新元件
8.1. 【Teleport】
- 什麼是Teleport?—— Teleport 是一種能夠將我們的元件html結構移動到指定位置的技術。
to裡面可以為id class 標籤等選擇器
<teleport to='body' >
<div class="modal" v-show="isShow">
<h2>我是一個彈窗</h2>
<p>我是彈窗中的一些內容</p>
<button @click="isShow = false">關閉彈窗</button>
</div>
</teleport>
8.2. 【Suspense】
實驗性功能
- 等待非同步元件時渲染一些額外內容,讓應用有更好的使用者體驗
比如現在子元件在發請求,如果半天出不來,那麼先展示fallback插槽裡面的內容,等非同步回來之後在展示上面的內容
- 使用步驟:
- 非同步引入元件
- 使用
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轉移到應用物件】
app.component
註冊全域性元件
app.config
全域性配置物件
所有元件都訪問得到
但是會報錯 vue官閘道器於這個屬性global有解釋
![image-20240621170712175](F:\前端\24前端\階段七 vue3\尚矽谷前端學科全套教程\Vue3快速上手.assets\image-20240621170712175.png)
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
指令在元件上的使用已經被重新設計,替換掉了v-bind.sync。
-
v-if
和v-for
在同一個元素身上使用時的優先順序發生了變化。(可以用在同一元件上) -
移除了
$on
、$off
和$once
例項方法。 -
移除了過濾器
filter
。 -
移除了
$children
例項propert
。......
vue官網
基於vue2 vue3的改變 面試暢聊
本文由部落格一文多發平臺 OpenWrite 釋出!