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月,最新的公開版 ...
1. Vue3簡介
-
2020年9月18日,
Vue.js
發佈版3.0
版本,代號:One Piece
(n -
截止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
是追加歷史記錄(預設值)。rep