新的網(wǎng)站平臺如何做地推百度關(guān)鍵詞工具在哪里
文章目錄
- 學習鏈接
- 1. Vue3簡介
- 1.1. 性能的提升
- 1.2.源碼的升級
- 1.3. 擁抱TypeScript
- 1.4. 新的特性
- 2. 創(chuàng)建Vue3工程
- 2.1. 基于 vue-cli 創(chuàng)建
- 2.2. 基于 vite 創(chuàng)建(推薦)
- vite介紹
- 創(chuàng)建步驟
- 項目結(jié)構(gòu)
- 安裝插件
- 項目結(jié)構(gòu)
- 總結(jié)
- 2.3. 一個簡單的效果
- Person.vue
- App.vue
- 3. Vue3核心語法
- 3.1. OptionsAPI 與 CompositionAPI
- Options API 的弊端
- Composition API 的優(yōu)勢
- 3.2. 拉開序幕的 setup
- setup 概述
- setup 的返回值
- setup 與 Options API 的關(guān)系
- setup 語法糖
- 3.3. ref 創(chuàng)建:基本類型的響應式數(shù)據(jù)
- 3.4. reactive 創(chuàng)建:對象類型的響應式數(shù)據(jù)
- 3.5 ref 創(chuàng)建:對象類型的響應式數(shù)據(jù)
- 3.6. ref 對比 reactive
- 宏觀角度
- 區(qū)別
- 使用原則
- 3.7 toRefs 與 toRef
- 現(xiàn)象
- toRefs&toRef的使用
- 3.8 computed
- 3.9 watch
- 作用
- 特點
- 場景
- * 情況一
- * 情況二
- 示例1
- 示例2
- * 情況三
- * 情況四
- 沒有監(jiān)視的代碼
- 監(jiān)視reactive定義的對象類型中的某個基本屬性
- 監(jiān)視reactive定義的對象類型中的某個對象屬性
- * 情況五
- 3.10 watchEffect
- 3.11. 標簽的 ref 屬性
- 用在普通DOM標簽上
- 用在組件標簽上(defineExpose)
- 3.12 回顧TS
- main.ts
- App.vue
- index.ts
- Person.vue
- 3.13 props(defineProps)
- App.vue
- index.ts
- Person.vue
- 3.14 生命周期
- App.vue
- Person.vue
- 3.15 自定義hooks
- 未使用hooks前
- App.vue
- Person.vue
- 使用hooks
- App.vue
- Person.vue
- hooks/useSum.ts
- hooks/useDog.ts
- 4.路由
- 4.1 路由的基本理解
- 4.2 基本切換效果
- 安裝vue-router
- 配置路由規(guī)則router/index.ts
- 使用router路由管理器main.ts
- 路由展示區(qū)App.vue
- 路由組件
- Home.vue
- New.vue
- About.vue
- 路由切換效果圖
- 4.3. 兩個注意點
- About.vue
- 4.4. 路由器工作模式
- 4.5. to的兩種寫法
- 4.6. 命名路由
- 4.7 嵌套路由
- main.ts
- router/index.ts
- App.vue
- News.vue
- Detail.vue
- 效果
- 4.8 路由傳參
- query參數(shù)
- params參數(shù)
- 4.9 路由的props配置
- 4.10 replace屬性
- 示例
- 4.11 編程式導航
- 示例
- 4.12 重定向
- 示例
- 5. pinia
- 5.1 準備一個效果
- main.ts
- App.vue
- Count.vue
- LoveTalk.vue
- 5.2 搭建 pinia 環(huán)境
- 使用步驟
- 5.3 存儲+讀取數(shù)據(jù)
- store/count.ts
- store/loveTalk.ts
- Count.vue
- LoveTalk.vue
- App.vue
- main.ts
- 5.4 修改數(shù)據(jù)(三種方式)
- 第一種方式
- count.ts
- Count.vue
- 第二種方式
- count.ts
- Count.vue
- 第三種方式
- count.ts
- Count.vue
- 5.5 storeToRefs用法
- LoveTalk.ts
- LoveTask.vue
- count.ts
- Count.vue
- 5.6 getters用法
- count.ts
- Count.vue
- 5.7 $subscribe的使用
- loveTalk.ts
- LoveTalk.vue
- 5.8 store組合式寫法
- loveTalk.js
- LoveTalk.vue
- 6. 組件通信
- 6.1 props
- Father.vue
- Child.vue
- 6.2 自定義事件
- Father.vue
- Child.vue
- 6.3 mitt
- emitter.ts
- Father.vue
- Child1.vue
- Child2.vue
- 6.4 v-model
- Father.vue
- AtguiguInput.vue
- 6.5 $attrs
- Father.vue
- Child.vue
- GrandChild.vue
- 6.6 r e f s 、 refs、 refs、parent、proxy
- Father.vue
- Child1.vue
- Child2.vue
- 6.7 provide、inject
- Father.vue
- Child.vue
- GrandChild.vue
- 6.8 pinia
- 6.9 slot插槽
- 1. 默認插槽
- Father.vue
- Category.vue
- 2. 具名插槽
- Father.vue
- Category.vue
- 3. 作用域插槽
- Father.vue
- Category.vue
- 7. 其它 API
- 7.1 shallowRef 與 shallowReactive
- shallowRef
- shallowReactive
- 示例
- 7.2 readonly 與 shallowReadonly
- readonly
- shallowReadonly
- 示例
- 7.3 toRaw 與 markRaw
- toRaw
- markRaw
- 示例
- 7.4 customRef
- 示例
- App.vue
- useMsgRef.ts
- 8. Vue3新組件
- 8.1 Teleport傳送門
- 示例
- App.vue
- Modal.vue
- 8.2 Suspense
- 示例
- App.vue
- Child.vue
- 8.3 全局API轉(zhuǎn)移到應用對象
- 示例
- 8.4 其他
學習鏈接
尚硅谷Vue3入門到實戰(zhàn),最新版vue3+TypeScript前端開發(fā)教程
Vue3+Vite4+Pinia+ElementPlus從0-1 web項目搭建
Vue3.2后臺管理系統(tǒng)
深入Vue3+TypeScript技術(shù)棧 coderwhy
尚硅谷Vue項目實戰(zhàn)硅谷甄選,vue3項目+TypeScript前端項目一套通關(guān)
基于Vue3最新標準,實現(xiàn)后臺前端綜合解決方案 imooc-admin源碼
Vue3 + vite + Ts + pinia + 實戰(zhàn) + 源碼 + electron - 百萬播放量哦
1. Vue3簡介
-
2020年9月18日,
Vue.js
發(fā)布版3.0
版本,代號:One Piece
-
經(jīng)歷了:4800+次提交、40+個RFC、600+次PR、300+貢獻者
-
官方發(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
實現(xiàn)響應式。 -
重寫虛擬
DOM
的實現(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
選項應始終被聲明為一個函數(shù) -
移除
keyCode
支持作為v-on
的修飾符…
-
2. 創(chuàng)建Vue3工程
2.1. 基于 vue-cli 創(chuàng)建
點擊查看 Vue-Cli 官方文檔,(基于vue-cli創(chuàng)建,其實就是基于webpack來創(chuàng)建vue項目)
備注:目前
vue-cli
已處于維護模式,官方推薦基于Vite
創(chuà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介紹
vite
是新一代前端構(gòu)建工具,官網(wǎng)地址:https://vitejs.cn,vite
的優(yōu)勢如下:
- 輕量快速的熱重載(
HMR
),能實現(xiàn)極速的服務啟動。 - 對
TypeScript
、JSX
、CSS
等支持開箱即用(不用配置,直接就可以用)。 - 真正的按需編譯,不再等待整個應用編譯完成。
webpack
構(gòu)建 與vite
構(gòu)建對比圖如下:
創(chuàng)建步驟
具體操作如下(點擊查看官方文檔)
## 1.創(chuàng)建命令(基于vite創(chuàng)建vue3項目,前提是需要安裝nodejs環(huán)境)
npm create vue@latest## 2.具體配置
## 配置項目名稱
√ 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
構(gòu)建過程如下:
訪問vue3項目如下:
項目結(jié)構(gòu)
安裝插件
安裝官方推薦的vscode
插件:
項目結(jié)構(gòu)
index.html
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><link rel="icon" href="/favicon.ico"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vite App</title></head><body><div id="app"></div><script type="module" src="/src/main.ts"></script></body>
</html>
main.ts
import './assets/main.css'// 引入createApp用于創(chuàng)建應用
import { createApp } from 'vue'// 引入App根組件
import App from './App.vue'createApp(App).mount('#app')
App.vue
<!-- 自己動手編寫的一個App組件 -->
<template><div class="app"><h1>你好啊!</h1></div>
</template><script lang="ts"> // 添加lang="ts", 里面寫ts或js都可以export default {name:'App' //組件名}</script><style>.app {background-color: #ddd;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;}
</style>
總結(jié)
Vite
項目中,index.html
是項目的入口文件,在項目最外層。- 加載
index.html
后,Vite
解析<script type="module" src="xxx">
指向的JavaScript
。 Vue3
在main.ts中是通過createApp
函數(shù)創(chuàng)建一個應用實例。
2.3. 一個簡單的效果
Vue3
向下兼容Vue2
語法,且Vue3
中的模板中可以沒有根標簽
Person.vue
<template><div class="person"><h2>姓名:{{name}}</h2><h2>年齡:{{age}}</h2><button @click="changeName">修改名字</button><button @click="changeAge">年齡+1</button><button @click="showTel">點我查看聯(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>
App.vue
<template><div class="app"><h1>你好啊!</h1><Person/></div>
</template><script lang="ts">import Person from './components/Person.vue'export default {name:'App', //組件名components:{Person} //注冊組件}
</script><style>.app {background-color: #ddd;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;}
</style>
3. Vue3核心語法
3.1. OptionsAPI 與 CompositionAPI
Vue2
的API
設計是Options
(配置)風格的。Vue3
的API
設計是Composition
(組合)風格的。
Options API 的弊端
Options
類型的 API
,數(shù)據(jù)、方法、計算屬性等,是分散在:data
、methods
、computed
中的,若想新增或者修改一個需求,就需要分別修改:data
、methods
、computed
,不便于維護和復用。
Composition API 的優(yōu)勢
可以用函數(shù)的方式,更加優(yōu)雅的組織代碼,讓相關(guān)功能的代碼更加有序的組織在一起。
3.2. 拉開序幕的 setup
setup 概述
介紹
setup
是Vue3
中一個新的配置項,值是一個函數(shù)。- 它是
Composition API
“表演的舞臺”,組件中所用到的:數(shù)據(jù)、方法、計算屬性、監(jiān)視…等等,均配置在setup
中。
特點如下:
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">點我查看聯(lián)系方式</button></div>
</template><script lang="ts">export default {name:'Person',// 生命周期函數(shù)beforeCreate(){console.log('beforeCreate')},setup(){// 先打印的setup..., 再打印的beforeCreate, 說明了setup函數(shù)與beforeCreate生命周期函數(shù)的執(zhí)行順序console.log('setup ...')// 【setup函數(shù)中的this是undefined】console.log(this); // undefined// 數(shù)據(jù),原來寫在data中【注意:此時的name、age、tel數(shù)據(jù)都不是響應式數(shù)據(jù)】// (不是響應式的意思是:當這些數(shù)據(jù)變化,并不會觸發(fā)dom更新,// 模板中應用這些變量的地方?jīng)]有重新渲染)let name = '張三'let age = 18let tel = '13888888888'// 方法,原來寫在methods中function changeName(){name = 'zhang-san' // 注意:此時這么修改name頁面是不變化的console.log(name) // (name確實改了,但name不是響應式的)}function changeAge(){age += 1 // 注意:此時這么修改age頁面是不變化的console.log(age) // (age確實改了,但age不是響應式的)}function showTel(){alert(tel)}// 返回一個對象,對象中的內(nèi)容,模板中可以直接使用(將數(shù)據(jù)、方法交出去,模板中才可以使用這些交出去的數(shù)據(jù)、方法)return {name,age,tel,changeName,changeAge,showTel}}}
</script>
setup 的返回值
- 若返回一個對象:則對象中的:屬性、方法等,在模板中均可以直接使用**(重點關(guān)注)。**
- 若返回一個函數(shù):則可以直接指定 自定義渲染的內(nèi)容,代碼如下:
<template><div class="person">我特么一點都不重要了</div>
</template><script lang="ts">export default {name:'Person',setup(){// setup的返回值也可以是一個渲染函數(shù)// (模板什么的都不重要了,直接在頁面上渲染成:你好啊!這幾個字)// return ()=>'哈哈'}}
</script><style scoped>.person {background-color: skyblue;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;}button {margin: 0 5px;}
</style>
setup 與 Options API 的關(guān)系
Vue2
的配置(data
、methos
…)中可以訪問到setup
中的屬性、方法。- 但在
setup
中不能訪問到Vue2
的配置(data
、methos
…)。 - 如果與
Vue2
沖突,則setup
優(yōu)先。
<template><div class="person"><h2>姓名:{{name}}</h2><h2>年齡:{{age}}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年齡</button><button @click="showTel">查看聯(lián)系方式</button><hr><h2>測試1:{{a}}</h2><h2>測試2:{{c}}</h2><h2>測試3:{vxwlu0yf4}</h2><button @click="b">測試</button></div>
</template><script lang="ts">export default {name:'Person',beforeCreate(){console.log('beforeCreate')},data(){return {a:100,// 在data配置項中, 可以使用this.name來使用setup中交出的數(shù)據(jù), 因為setup執(zhí)行時機更早。// 但是在setup中不能使用在data中定義的數(shù)據(jù)c:this.name, d:900,age:90}},methods:{b(){console.log('b')}},// setup可以與data、methods等配置項同時存在setup(){// 數(shù)據(jù),原來是寫在data中的,此時的name、age、tel都不是響應式的數(shù)據(jù)let name = '張三'let age = 18let tel = '13888888888'// 方法function changeName() {name = 'zhang-san' // 注意:這樣修改name,頁面是沒有變化的console.log(name) // name確實改了,但name不是響應式的}function changeAge() {age += 1 // 注意:這樣修改age,頁面是沒有變化的console.log(age) // age確實改了,但age不是響應式的}function showTel() {alert(tel)}// 將數(shù)據(jù)、方法交出去,模板中才可以使用return {name,age,tel,changeName,changeAge,showTel}// setup的返回值也可以是一個渲染函數(shù)// return ()=>'哈哈'}}
</script><style scoped>.person {background-color: skyblue;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;}button {margin: 0 5px;}
</style>
setup 語法糖
setup
函數(shù)有一個語法糖,這個語法糖,可以讓我們把setup
獨立出去,代碼如下:
<template><div class="person"><h2>姓名:{{name}}</h2><h2>年齡:{{age}}</h2><button @click="changName">修改名字</button><button @click="changAge">年齡+1</button><button @click="showTel">點我查看聯(lián)系方式</button></div></template><!-- 專門單個弄個script標簽, 特地來配置組件的名字 -->
<script lang="ts">export default {name:'Person',}
</script><!-- 下面的寫法是setup語法糖 -->
<!-- 1. 相當于寫了setup函數(shù); 2. 相當于自動把其中定義的變量交出去(包括里面引入的其它組件也會交出去, 可以在模板中使用引入的組件))-->
<script setup lang="ts">console.log(this) // undefined// 數(shù)據(jù)(注意:此時的name、age、tel都不是響應式數(shù)據(jù))let name = '張三'let age = 18let tel = '13888888888'// 方法function changName(){name = '李四'//注意:此時這么修改name頁面是不變化的}function changAge(){console.log(age)age += 1 //注意:此時這么修改age頁面是不變化的}function showTel(){alert(tel)}
</script>
擴展:上述代碼,還需要編寫一個不寫
setup
的script
標簽,去指定組件名字,比較麻煩,我們可以借助vite
中的插件簡化
- 第一步:
npm i vite-plugin-vue-setup-extend -D
- 第二步:
vite.config.ts
import { fileURLToPath, URL } from 'node:url'import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import VueSetupExtend from 'vite-plugin-vue-setup-extend'// https://vitejs.dev/config/
export default defineConfig({plugins: [vue(),VueSetupExtend(),],resolve: {alias: {'@': fileURLToPath(new URL('./src', import.meta.url))}}
})
- 第三步:
<script setup lang="ts" name="Person">
3.3. ref 創(chuàng)建:基本類型的響應式數(shù)據(jù)
- **作用:**定義響應式變量。
- 語法:
let xxx = ref(初始值)
。 - **返回值:**一個
RefImpl
的實例對象,簡稱ref對象
或ref
,ref
對象的value
屬性是響應式的。 - 注意點:
JS
中操作數(shù)據(jù)需要:xxx.value
,但模板中不需要.value
,直接使用即可。- 對于
let name = ref('張三')
來說,name
不是響應式的,name.value
是響應式的。
<template><div class="person"><!-- 模板中直接使用, 不需要.value --><h2>姓名:{{name}}</h2><h2>年齡:{{age}}</h2><h2>電話:{{tel}}</h2><button @click="changeName">修改名字</button><button @click="changeAge">年齡+1</button><button @click="showTel">點我查看聯(lián)系方式</button></div>
</template><!-- 使用了setup語法糖, 會自動將定義的變量和方法交出去, 以供給模板使用 -->
<script setup lang="ts" name="Person">// 引入vue中的ref函數(shù)import { ref } from 'vue'// name和age是一個RefImpl的實例對象,簡稱ref對象,它們的value屬性是響應式的。//(所謂的響應式指的是, 對數(shù)據(jù)的改變后, 能夠讓模板中使用該數(shù)據(jù)的地方得到重新渲染更新)// ref是1個函數(shù), 向這個ref函數(shù)中傳入?yún)?shù), 返回的是1個RefImpl的實例對象let name = ref('張三')let age = ref(18)// tel就是一個普通的字符串,不是響應式的let tel = '13888888888'function changeName(){// JS中操作ref對象時候需要.valuename.value = '李四' // 頁面得到刷新console.log(name.value)// 注意:name不是響應式的,name.value是響應式的,所以如下代碼并不會引起頁面的更新。// name = ref('zhang-san')}function changeAge(){// JS中操作ref對象時候需要.valueage.value += 1 // 頁面得到刷新console.log(age.value)}function showTel(){// tel是普通數(shù)據(jù) tel += '1' // tel的確改了, 但頁面并未刷新alert(tel)}
</script>
3.4. reactive 創(chuàng)建:對象類型的響應式數(shù)據(jù)
- 作用:定義一個響應式對象(基本類型不要用它,要用
ref
,否則報錯) - 語法:
let 響應式對象= reactive(源對象)
。 - **返回值:**一個
Proxy
的實例對象,簡稱:響應式對象。 - 注意點:
reactive
定義的響應式數(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ù)// reactive是1個函數(shù), 向這個reactive函數(shù)中傳入?yún)?shù)(傳入對象或數(shù)組), 返回的是1個Proxy的實例對象//(Proxy是原生Js就有的函數(shù))// reactive函數(shù)中傳入對象let car = reactive({ brand: '奔馳', price: 100 }) console.log('car', car); // car Proxy {brand: '奔馳', price: 100}// reactive函數(shù)傳入數(shù)組let games = reactive([ { id: 'ahsgdyfa01', name: '英雄聯(lián)盟' },{ id: 'ahsgdyfa02', name: '王者榮耀' },{ id: 'ahsgdyfa03', name: '原神' }])// reactive定義的響應式數(shù)據(jù)是 深層次 的let obj = reactive({a: {b: {c: {d: 666}}}})// 修改對象中的屬性(修改使用reactive包裹對象后返回的對象)function changeCarPrice() {car.price += 10}// 修改數(shù)組中的對象的屬性(修改使用reactive包裹數(shù)組后返回的對象)function changeFirstGame() {games[0].name = '流星蝴蝶劍'}function test() {obj.a.b.c.d = 999}
</script>
3.5 ref 創(chuàng)建:對象類型的響應式數(shù)據(jù)
- 其實
ref
接收的數(shù)據(jù)可以是:基本類型、對象類型。 - 若
ref
接收的是對象類型,內(nèi)部其實也是調(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,reactive } from 'vue'// 使用ref定義對象類型響應式數(shù)據(jù)let car = ref({ brand: '奔馳', price: 100 })// 使用reactive定義對象類型響應式數(shù)據(jù)let car2 = reactive({brand: '奔馳', price: 100})// reactive只能用來定義對象類型的響應式數(shù)據(jù)// let name = reactive('zhangsan') // 錯誤, value cannot be made reactive: zhangsan// 使用ref定義對象(數(shù)組)類型響應式數(shù)據(jù)let games = ref([{ id: 'ahsgdyfa01', name: '英雄聯(lián)盟' },{ id: 'ahsgdyfa02', name: '王者榮耀' },{ id: 'ahsgdyfa03', name: '原神' }])// 使用ref定義對象類型響應式數(shù)據(jù)也是深層次的let obj = ref({a: {b: {c: {d: 666}}}})// 若ref接收的是對象類型,內(nèi)部其實也是使用的reactive函數(shù)console.log(car) // RefImpl {__v_isShallow: false, dep: undefined, // __v_isRef: true, _rawValue: {…}, _value: Proxy}console.log(car.value) // Proxy {brand: '奔馳', price: 100}console.log(car2) // Proxy {brand: '奔馳', price: 100}function changeCarPrice() {// 使用ref函數(shù)定義的響應式數(shù)據(jù), 在js操作時, 需要帶上.value, 才能碰到內(nèi)部的Proxy對象car.value.price += 10console.log(car.value.price);}function changeFirstGame() {// 使用ref函數(shù)定義的響應式數(shù)據(jù), 在js操作時, 需要帶上.value, 才能碰到內(nèi)部的Proxy對象games.value[0].name = '流星蝴蝶劍'console.log(games.value); // Proxy {0: {…}, 1: {…}, 2: {…}}}function test() {// 使用ref函數(shù)定義的響應式數(shù)據(jù), 在js操作時, 需要帶上.value, 才能碰到內(nèi)部的Proxy對象obj.value.a.b.c.d = 999}</script>
3.6. ref 對比 reactive
宏觀角度
-
ref可以定義:基本類型、對象類型的響應式數(shù)據(jù)
-
reactive只能定義:對象類型的響應式數(shù)據(jù)
區(qū)別
-
ref創(chuàng)建的變量必須使用
.value
(可以使用volar
插件自動添加.value
)。可以在齒輪->設置->擴展->volar中勾選
,它會在使用ref創(chuàng)建的變量時,自動添加上.value
-
reactive重新分配一個新對象,會失去響應式(可以使用
Object.assign
去整體替換)。<template><div class="person"><h2>汽車信息:一臺{{ car.brand }}汽車,價值{{ car.price }}萬</h2><button @click="changeBrand">改品牌</button><button @click="changePrice">改價格</button><button @click="changeCar">改car</button></div> </template><script lang="ts" setup name="Person">import { ref,reactive } from 'vue'let car = reactive({brand:'奔馳', price:100})function changeBrand() {// 正常修改car的brand, 并且是響應式car.brand = '寶馬' }function changePrice() {// 正常修改car的price, 并且是響應式car.price += 10 }function changeCar() {// 錯誤做法1// 不可以直接給reactive重新分配一個新對象,這會讓car直接失去響應式// car = {brand:'奧托', price:10}// 錯誤做法2// 這樣也不行, 因為模板中用的car是上面定義的響應式對象, // 現(xiàn)在car指向的是1個新的響應式對象, 而模板中壓根就沒有使用這個新的響應式對象// car = reactive({brand:'奧托', price:10})// 正確做法(car仍然是響應式的)// API介紹: Object.assign(obj1, obj2, obj3, ..), // 將obj2中的每一組屬性和值設置到obj1中, 然后obj3的每一組屬性和值設置到obj1中Object.assign(car, {brand:'奧托', price:10}) }</script>
<template><div class="person"><h2>汽車信息:一臺{{ car.brand }}汽車,價值{{ car.price }}萬</h2><button @click="changeBrand">改品牌</button><button @click="changePrice">改價格</button><button @click="changeCar">改car</button></div> </template><script lang="ts" setup name="Person">import { ref,reactive } from 'vue'let car = ref({brand:'奔馳', price:100})function changeBrand() {// 正常修改car的brand, 并且是響應式car.value.brand = '寶馬' }function changePrice() {// 正常修改car的price, 并且是響應式car.value.price += 10 }function changeCar() {// 錯誤做法1// 不能直接給car換了個ref, 因為模板中壓根就沒有使用這個新的RefImpl對象// car = ref({brand:'奧托', price:10})// 正確做法1(car仍然是響應式的)// API介紹: Object.assign(obj1, obj2, obj3, ..), 將obj2中的每一組屬性和值設置到obj1中, // 然后obj3的每一組屬性和值設置到obj1中// Object.assign(car.value, {brand:'奧托', price:10})// 正確做法2//(這里相比于對car使用reactive定義而言, 使用ref定義則可以直接給car.value整體賦值// 原因在于car.value獲取的是Proxy響應式對象, 凡是對Proxy響應式對象的操作都可以被攔截到)car.value = {brand:'奧托', price:10}}</script>
使用原則
-
若需要一個基本類型的響應式數(shù)據(jù),必須使用
ref
。 -
若需要一個響應式對象,層級不深,
ref
、reactive
都可以。 -
若需要一個響應式對象,且層級較深,推薦使用
reactive
。
3.7 toRefs 與 toRef
- 作用:將一個響應式對象中的每一個屬性,轉(zhuǎn)換為
ref
對象。 - 備注:
toRefs
與toRef
功能一致,但toRefs
可以批量轉(zhuǎn)換。
現(xiàn)象
對響應式對象直接結(jié)構(gòu)賦值,得到的數(shù)據(jù)不是響應式的
<template><div class="person"><h2>姓名:{{ person.name }} {{ name }}</h2><h2>年齡:{{ person.age }} {{ age }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年齡</button></div>
</template><script lang="ts" setup name="Person2">import { ref, reactive, toRefs, toRef } from 'vue'// 數(shù)據(jù)let person = reactive({ name: '張三', age: 18 })console.log(person); // Proxy {name: '張三', age: 18}// 這里的解構(gòu)賦值其實就等價于: let name = person.name; let age = person.age;// 只是記錄了此時person.name、person.age的值, 僅此而已// 因此, 此處使用結(jié)構(gòu)賦值語法獲取的name和age都不是響應式的let {name, age } = personconsole.log(name, age); // 張三 18// 方法function changeName() {name += '~'console.log(name, person.name); // 變化的是name, 而person.name仍然未修改}function changeAge() {age += 1console.log(age, person.age); // 變化的是age, 而person.age仍然未修改}</script>
toRefs&toRef的使用
通過toRefs將person對象中的所有屬性都批量取出, 且依然保持響應式的能力
<template>
<div class="person"><h2>姓名:{{ person.name }}</h2><h2>年齡:{{ person.age }}</h2><h2>性別:{{ person.gender }} {{ gender }}</h2><button @click="changeName">修改名字</button><button @click="changeAge">修改年齡</button><button @click="changeGender">修改性別</button><button @click="changeGender2">修改性別2</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對象中的所有屬性都批量取出, 且依然保持響應式的能力//(使用toRefs從person這個響應式對象中,解構(gòu)出name、age, 且name和age依然是響應式的,// name和gender的值是ref類型, 其value值指向的是person.name和person.age,// 對name.value和對age.value的修改將會修改person.name和person.age, 并且會頁面渲染刷新)let { name, age } = toRefs(person)console.log(name.value, name); // '張三' ObjectRefImpl {_object: Proxy, _key: 'name', // _defaultValue: undefined, __v_isRef: true}console.log(age.value, age.value); // 18 ObjectRefImpl {_object: Proxy, _key: 'age', // _defaultValue: undefined, __v_isRef: true}console.log(toRefs(person)); // {name: ObjectRefImpl, age: ObjectRefImpl, // gender: ObjectRefImpl}// 通過toRef將person對象中的gender屬性取出,且依然保持響應式的能力let gender = toRef(person, 'gender')console.log(gender, gender.value); // ObjectRefImpl {_object: Proxy, _key: 'gender', // _defaultValue: undefined, __v_isRef: true} '男'// 方法function changeName() {// 此處修改name.value, 將會修改person.name, 并且頁面會刷新person.name的值name.value += '~'console.log(name.value, person.name);}function changeAge() {// 此處修改age.value, 將會修改person.age, 并且頁面會刷新person.age的值age.value += 1console.log(age.value, person.age);}function changeGender() {// 此處修改gender.value, 將會修改person.age, 并且頁面會刷新person.gender的值gender.value = '女'console.log(gender.value, person.gender);}function changeGender2() {// 此處對person.gender的修改, 將會修改上面的let gender = toRef(person, 'gender')// 并且頁面會刷新person.gender和gender的值person.gender = '男'console.log(gender.value, person.gender);}
</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">// 引入computed計算屬性函數(shù)
import { ref, computed } from 'vue'let firstName = ref('zhang')
let lastName = ref('san')// 計算屬性——只讀取,不修改
/*
// 1. 使用時, 在computed中傳入1個函數(shù)。在模板中, 直接使用計算屬性即可。
// 2. 當計算屬性依賴的數(shù)據(jù)只要發(fā)生變化, 它就會重新計算, 如果頁面中有使用到該計算屬性, 那么就會重新渲染模板
// 3. 只會計算1次, 后面會使用緩存, 而方法是沒有緩存的
let fullName = computed(()=>{return firstName.value + '-' + lastName.value
})
console.log(fullName); // ComputedRefImpl {dep: undefined, __v_isRef: true, // __v_isReadonly: true, effect: ReactiveEffect, _setter: ?, …}*/// 計算屬性——既讀取又修改
let fullName = computed({// 讀取get() {// 當firstName或lastName變化時, 計算屬性會重新計算, 并刷新頁面渲染return firstName.value + '-' + lastName.value},// 修改// 當修改計算屬性時(或者說給計算屬性賦值時, 注意要.value), 此方法會被調(diào)用set(val) {console.log('有人修改了fullName', val)firstName.value = val.split('-')[0]lastName.value = val.split('-')[1]}
})function changeFullName() {// 修改fullName計算屬性(會觸發(fā)計算屬性中set方法的調(diào)用)fullName.value = 'li-si'
}
</script>
3.9 watch
作用
監(jiān)視數(shù)據(jù)的變化(和Vue2
中的watch
作用一致)
特點
Vue3
中的watch
只能監(jiān)視以下四種數(shù)據(jù):
-
ref定義的數(shù)據(jù)。
-
reactive定義的數(shù)據(jù)。
-
函數(shù)返回一個值(getter函數(shù),所謂的getter函數(shù)就是能返回一個值的函數(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>當前求和為:{{ sum }}</h2><button @click="changeSum">點我sum+1</button></div>
</template><script lang="ts" setup name="Person">// 引入watch監(jiān)視函數(shù)
import { ref, watch } from 'vue'// 數(shù)據(jù)
let sum = ref(0)// 方法
function changeSum() {sum.value += 1
}// 監(jiān)視,情況一:監(jiān)視【ref】定義的【基本類型】數(shù)據(jù)
//(注意:這里監(jiān)視寫的是sum, 而不是sum.value哦)
const stopWatch = watch(sum, (newValue, oldValue) => {console.log('sum變化了', newValue, oldValue) // 注意: 這里也沒帶.value哦if (newValue >= 10) {// 解除監(jiān)視(即: 當調(diào)用此方法后, 不會再監(jiān)視sum的變化了, 也就是當sum變化時, 當前的監(jiān)視函數(shù)不再執(zhí)行了)stopWatch()}
})</script><style scoped>
...
</style>
* 情況二
監(jiān)視ref定義的【對象類型】數(shù)據(jù):直接寫數(shù)據(jù)名,監(jiān)視的是對象的【地址值】。若想監(jiān)視對象內(nèi)部的數(shù)據(jù),要手動開啟深度監(jiān)視。
注意:
-
若修改的是
ref
定義的對象中的屬性,newValue
和oldValue
都是新值,因為它們是同一個對象。 -
若修改整個
ref
定義的對象,newValue
是新值,oldValue
是舊值,因為不是同一個對象了。
示例1
<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 += '~' // 當修改person.value.name時, 監(jiān)視函數(shù)未被觸發(fā)
}function changeAge() {person.value.age += 1 // 當修改person.value.age時, 監(jiān)視函數(shù)也未被觸發(fā)
}function changePerson() {person.value = { name: '李四', age: 90 } // 當整體修改person.value時, 此時監(jiān)視函數(shù)被觸發(fā)
} // (因為監(jiān)視的是對象的地址值, 所以這里每次修改都會觸發(fā)監(jiān)視函數(shù))/* 監(jiān)視,情況一:監(jiān)視【ref】定義的【對象類型】數(shù)據(jù),監(jiān)視的是對象的地址值。watch的第一個參數(shù)是:被監(jiān)視的數(shù)據(jù)watch的第二個參數(shù)是:監(jiān)視的回調(diào)
*/
watch(person, (newValue, oldValue) => {console.log('person變化了', newValue, oldValue)// 一直調(diào)用changePerson方法, 控制臺如下輸出// person變化了 Proxy {name: '李四', age: 90} Proxy {name: '張三', age: 18}// person變化了 Proxy {name: '李四', age: 90} Proxy {name: '李四', age: 90}// person變化了 Proxy {name: '李四', age: 90} Proxy {name: '李四', age: 90}// person變化了 Proxy {name: '李四', age: 90} Proxy {name: '李四', age: 90}// ...
})</script><style scoped>
...
</style>
示例2
<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 += '~'// 因為開啟了深度監(jiān)視, 當修改person.value.name時, 監(jiān)視函數(shù)被觸發(fā)
} //(但由于原對象并未修改, 所以監(jiān)視函數(shù)中輸出的newVal和oldVal是一樣的)// 每次調(diào)用changeName都修改, 變化如下:// person變化了 Proxy {name: '張三~', age: 18} Proxy {name: '張三~', age: 18}// person變化了 Proxy {name: '張三~~', age: 18} Proxy {name: '張三~~', age: 18}// person變化了 Proxy {name: '張三~~', age: 18} Proxy {name: '張三~~', age: 18}// ...
function changeAge() {person.value.age += 1 // 因為開啟了深度監(jiān)視, 當修改person.value.name時, 監(jiān)視函數(shù)被觸發(fā)
} //(但由于原對象并未修改, 所以監(jiān)視函數(shù)中輸出的newVal和oldVal是一樣的)// 每次調(diào)用changeName都修改, 變化如下:// person變化了 Proxy {name: '張三', age: 19} Proxy {name: '張三', age: 19}// person變化了 Proxy {name: '張三', age: 20} Proxy {name: '張三', age: 20}// person變化了 Proxy {name: '張三', age: 21} Proxy {name: '張三', age: 21}// ...function changePerson() {person.value = { name: '李四', age: 90 }// 當整體修改person.value時, 監(jiān)視函數(shù)被觸發(fā)//(但由于原對象都改了, 所以監(jiān)視函數(shù)中輸出的newVal和oldVal是不一樣的)// 每次調(diào)用changeName都修改, 變化如下:
} // person變化了 Proxy {name: '李四', age: 90} Proxy {name: '張三', age: 18}// person變化了 Proxy {name: '李四', age: 90} Proxy {name: '李四', age: 90}// person變化了 Proxy {name: '李四', age: 90} Proxy {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, immediate: true })</script><style scoped>
.person {background-color: skyblue;box-shadow: 0 0 10px;border-radius: 10px;padding: 20px;
}button {margin: 0 5px;
}li {font-size: 20px;
}
</style>
* 情況三
監(jiān)視reactive
定義的【對象類型】數(shù)據(jù),且默認開啟了深度監(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 += '~'// 每次調(diào)用changeName都修改, 變化如下:// person變化了 Proxy {name: '張三~', age: 18} Proxy {name: '張三~', age: 18}// person變化了 Proxy {name: '張三~~', age: 18} Proxy {name: '張三~~', age: 18}// person變化了 Proxy {name: '張三~~~', age: 18} Proxy {name: '張三~~~', age: 18}// ...//(如上結(jié)果, // 1. 證明監(jiān)視到了person的name // 2. oldVal和newVal是一樣的輸出, 是因為雖然監(jiān)測到person的變化, 但oldVal和newVal是同一對象, 從這來說并未改變)}function changeAge(){person.age += 1// 每次調(diào)用changeAge都修改, 變化如下:// person變化了 Proxy {name: '張三', age: 19} Proxy {name: '張三', age: 19}// person變化了 Proxy {name: '張三', age: 20} Proxy {name: '張三', age: 20}// person變化了 Proxy {name: '張三', age: 21} Proxy {name: '張三', age: 21}// ...//(如上結(jié)果, // 1. 證明監(jiān)視到了person的age// 2. oldVal和newVal是一樣的輸出, 是因為雖然監(jiān)測到person的變化, 但oldVal和newVal是同一對象, 從這來說并未改變)}function changePerson(){// 此處注意: 使用reactive函數(shù)定義的數(shù)據(jù), 不能直接替換, 可以如下方式對person中的屬性做批量修改 Object.assign(person,{name:'李四',age:80})// 多次調(diào)用changePerson, 僅有1次監(jiān)視到到修改, 變化如下:// person變化了 Proxy {name: '李四', age: 80} Proxy {name: '李四', age: 80}//(如上結(jié)果, // 1. 證明監(jiān)視到了person的name和age的改變// 2. oldVal和newVal是一樣的輸出, 是因為雖然監(jiān)測到person的變化, 但oldVal和newVal仍是同一對象, 從這來說并未改變)}function test(){obj.a.b.c = 888// 此處證明watch監(jiān)控reactive定義的對象類型數(shù)據(jù), 默認是開啟了深度監(jiān)視的}// 監(jiān)視,情況三:監(jiān)視【reactive】定義的【對象類型】數(shù)據(jù),且默認是開啟深度監(jiān)視的(隱式創(chuàng)建了深層次的監(jiān)聽, 無法關(guān)閉)watch(person,(newValue,oldValue)=>{console.log('person變化了',newValue,oldValue)})watch(obj,(newValue,oldValue)=>{console.log('Obj變化了',newValue,oldValue)})</script><style scoped>
...
</style>
* 情況四
監(jiān)視ref
或reactive
定義的【對象類型】數(shù)據(jù)中的某個屬性,注意點如下:
- 若該屬性值不是【對象類型】,需要寫成函數(shù)形式。
- 若該屬性值是依然是【對象類型】,可直接編,也可寫成函數(shù),建議寫成函數(shù)。
結(jié)論:監(jiān)視的要是對象里的屬性,那么最好寫函數(shù)式。(注意點:若是對象,監(jiān)視的是地址值;需要關(guān)注對象內(nèi)部,則需要手動開啟深度監(jiān)視。)
沒有監(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是使用reactive定義的, 所以person整體不能改(改是可以改, 但是不再響應式了, // 所以說不能整體直接改), // 但是person里面的car屬性可以改, 因此可以如下改person.car = { c1: '雅迪', c2: '愛瑪' }}</script><style scoped>...
</style>
監(jiān)視reactive定義的對象類型中的某個基本屬性
<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 += '~'// 一直調(diào)用changeName方法, 控制臺如下輸出// person.name變化了 張三~ 張三// person.name變化了 張三~~ 張三~// person.name變化了 張三~~~ 張三~~// ...}function changeAge() {person.age += 1}function changeC1() {person.car.c1 = '奧迪'}function changeC2() {person.car.c2 = '大眾'}function changeCar() {// 注意此處: 因為person是使用reactive定義的, 所以person整體不能改(改是可以改, 但是不再響應式了, // 所以說不能整體直接改), // 但是person里面的car屬性可以改, 因此可以如下改person.car = { c1: '雅迪', c2: '愛瑪' }}// 監(jiān)視,情況四:監(jiān)視響應式對象中的某個屬性,且該屬性是基本類型的,要寫成函數(shù)式(不能直接寫person.name哦)//(如下監(jiān)視, 將會只監(jiān)視person的name屬性的變化, // 當person的name屬性發(fā)生變化時, 將會觸發(fā)監(jiān)聽函數(shù)執(zhí)行, 其它屬性變化不會觸發(fā)監(jiān)聽函數(shù)的執(zhí)行)watch(()=> person.name,(newValue,oldValue)=>{console.log('person.name變化了',newValue,oldValue)}) // 錯誤寫法, 因為person的name屬性是基本類型, 所以不能直接寫為第1個參數(shù), 應該要用函數(shù)包一下/*watch(person.name,(newValue,oldValue)=>{console.log('person.name變化了',newValue,oldValue)})*/// 監(jiān)視person的car屬性中的c1屬性//(當調(diào)用changeC1方法時, 此處能夠監(jiān)測到person.car.c1的改變;// 多次調(diào)用changeC1方法, 此處只監(jiān)測到了1次, 因為后面都沒改person.car.c1的值;// 當調(diào)用changeCar方法, 此處能夠監(jiān)測到person.car.c1的改變;// 多次調(diào)用changeCar方法, 此處只監(jiān)測到了1次, 因為后面都沒改person.car.c1的值;)watch(()=> person.car.c1,(newValue,oldValue)=>{console.log('person.car.c1變化了',newValue,oldValue)})// 錯誤寫法, 因為person的car.c1屬性是基本類型, 所以不能直接寫為第1個參數(shù), 應該要用函數(shù)包一下/*watch(person.car.c1,(newValue,oldValue)=>{console.log('person.car.c1變化了',newValue,oldValue)})*/</script><style scoped>
...
</style>
監(jiān)視reactive定義的對象類型中的某個對象屬性
<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是使用reactive定義的, 所以person整體不能改(改是可以改, 但是不再響應式了, // 所以說不能整體直接改), // 但是person里面的car屬性可以改, 因此可以如下改person.car = { c1: '雅迪', c2: '愛瑪' }}// 監(jiān)視,情況四:監(jiān)視響應式對象中的某個屬性,且該屬性是對象類型的,可以直接寫,也能寫函數(shù),更推薦寫函數(shù)// 建議寫成函數(shù)的形式// 當調(diào)用changeC1或changeC2方法時, 會觸發(fā)此處的監(jiān)測函數(shù)執(zhí)行// 當調(diào)用changeCar方法時, 會觸發(fā)此處的監(jiān)測函數(shù)執(zhí)行// 【最佳實踐】(函數(shù)式來開啟對person.car的地址值的監(jiān)測, 然后deep:true開啟對該對象的深度監(jiān)視)watch(() => person.car, (newValue, oldValue) => {console.log('person.car變化了', newValue, oldValue)}, { deep: true })// 如果寫成下面這樣, 監(jiān)測的其實是person.car的地址值, 只有在person.car整體改變時, 才會觸發(fā)此處的監(jiān)測函數(shù)執(zhí)行// 當調(diào)用changeC1或changeC2方法時, 不會觸發(fā)此處的監(jiān)測函數(shù)執(zhí)行/* watch(() => person.car, (newValue, oldValue) => {console.log('person.car變化了', newValue, oldValue)}) */// 如果寫成下面這樣(直接寫的做法), 那么當調(diào)用changeCar方法時, 不會觸發(fā)此處的監(jiān)測函數(shù)執(zhí)行// 當調(diào)用changeC1或changeC2方法時, 會觸發(fā)此處的監(jiān)測函數(shù)執(zhí)行//(因為person.car是person中的對象類型屬性, 因此這里可以直接寫)/* watch(person.car, (newValue, oldValue) => {console.log('person.car變化了', newValue, oldValue)}) */</script><style scoped>...
</style>
* 情況五
監(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ù)//(person.name是基本類型, 所以要寫成函數(shù)式; person.car是對象類型, 所以可以直接寫;// 這里的newVal和oldVal都是數(shù)組, 跟監(jiān)視的2個源相對應; // deep開啟深度監(jiān)視, 不止可以監(jiān)視地址值, 還包括內(nèi)部屬性的變化;)watch([()=>person.name, person.car],(newValue, oldValue)=>{console.log('person.car變化了',newValue,oldValue)},{deep:true})</script><style scoped>
...
</style>
3.10 watchEffect
官網(wǎng):立即運行一個函數(shù),同時響應式地追蹤其依賴,并在依賴更改時重新執(zhí)行該函數(shù)。
watch
對比watchEffect
都能監(jiān)聽響應式數(shù)據(jù)的變化,不同的是監(jiān)聽數(shù)據(jù)變化的方式不同
watch
:要明確指出監(jiān)視的數(shù)據(jù)
watchEffect
:不用明確指出監(jiān)視的數(shù)據(jù)(函數(shù)中用到哪些屬性,那就監(jiān)視哪些屬性)。
<template><div class="person"><h1>需求:水溫達到50℃,或水位達到20cm,則聯(lián)系服務器</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實現(xiàn),需要明確的指出要監(jiān)視:temp、heightwatch([temp,height],(value)=>{// 從value中獲取最新的temp值、height值const [newTemp,newHeight] = value// 室溫達到50℃,或水位達到20cm,立刻聯(lián)系服務器if(newTemp >= 50 || newHeight >= 20){console.log('聯(lián)系服務器')}})// 用watchEffect實現(xiàn),不用明確的指出要監(jiān)視變量// 1. 它會從監(jiān)聽函數(shù)中自動分析需要監(jiān)視的數(shù)據(jù) (而watch則需要指定需要監(jiān)視的數(shù)據(jù))// 2. 一上來就會執(zhí)行1次函數(shù)const stopWtach = watchEffect(()=>{// 室溫達到50℃,或水位達到20cm,立刻聯(lián)系服務器if(temp.value >= 50 || height.value >= 20){console.log(document.getElementById('demo')?.innerText)console.log('聯(lián)系服務器')}// 水溫達到100,或水位達到50,取消監(jiān)視if(temp.value === 100 || height.value === 50){console.log('清理了')stopWtach()}})
</script>
3.11. 標簽的 ref 屬性
作用:用于注冊模板引用。
-
用在普通
DOM
標簽上,獲取的是DOM
節(jié)點。 -
用在組件標簽上,獲取的是組件實例對象。
用在普通DOM標簽上
<template><div class="person"><!-- ref標記在普通DOM標簽上 --><h1 ref="title1">尚硅谷</h1><h2 ref="title2">前端</h2><h3 ref="title3">Vue</h3><input type="text" ref="inpt"> <br><br><button @click="showLog">點我打印內(nèi)容</button></div>
</template><script lang="ts" setup name="Person">import {ref} from 'vue'let title1 = ref() // 使用ref來獲取對應的節(jié)點, 其中title1要與對應節(jié)點的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>
用在組件標簽上(defineExpose)
defineExpose它屬于宏函數(shù),不需要引入
<!-- 父組件App.vue -->
<template><!-- ref標記在組件標簽上 --><Person ref="ren"/><button @click="test">測試</button></template><script lang="ts" setup name="App">// 在setUp中不需要注冊Person組件, 直接使用即可import Person from './components/Person.vue'import {ref} from 'vue'// 變量名需要與ref標記的值相同let ren = ref()function test(){// 需要子組件通過defineExpose暴露出來的屬性或方法, 父組件才可以在這里訪問到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 回顧TS
main.ts
// 引入createApp用于創(chuàng)建應用
import { createApp } from 'vue'// 引入App根組件
import App from './App.vue'createApp(App).mount('#app')
App.vue
<template><Person/>
</template><script lang="ts" setup name="App">import Person from '@/components/Person.vue'
</script>
index.ts
在src下創(chuàng)建types文件夾,并在這個文件夾中創(chuàng)建如下index.ts文件。
在其中定義接口和自定義泛型
// 定義一個接口,用于限制person對象的具體屬性
export interface PersonInter {id: string,name: string,age: number,x?: number /* x是可選屬性, 該類型中可以有該屬性, 也可以無該屬性 */
}// 一個自定義類型
// export type Persons = Array<PersonInter>
export type Persons = PersonInter[] // 與上面等價
Person.vue
注意把vetur這個插件給禁掉, 否則,老是有飄紅。就開啟本篇中上述的推薦的插件即可。
<template><div class="person"></div>
</template><script lang="ts" setup name="Person">// 引入接口 或 自定義類型 的時候, 需要在前面加上type; import { type PersonInter, type Persons } from '@/types'// 定義1個變量, 它要符合PersonInter接口let person: PersonInter = {id: 'a01', name: 'john', age:60}// 定義1個數(shù)組, 首先它是個數(shù)組, 并且里面元素類型都是符合PersonInter接口的(如果里面有屬性名寫錯會有飄紅提示)let personList: Array<PersonInter> = [{id: 'a01', name: 'john', age:60}]// 定義1個數(shù)組, 它符合 Persons 自定義類型(如果里面有屬性名寫錯會有飄紅提示)let personList2: Persons = [{id: 'a01', name: 'john', age:60}]</script><style scoped></style>
3.13 props(defineProps)
defineProps它屬于宏函數(shù),不需要引入
App.vue
<template><!-- Person子組件定義了list屬性, 并且限定為Persons類型 --><Person :list="personList" />
</template><script lang="ts" setup name="App">import Person from '@/components/Person.vue'import {reactive} from 'vue'import {type Persons} from '@/types'let personList = reactive<Persons>([{ id: 'asudfysafd01', name: '張三', age: 18 },{ id: 'asudfysafd02', name: '李四', age: 20 },{ id: 'asudfysaf)d03', name: '王五', age: 22 }])</script>
index.ts
// 定義一個接口,用于限制person對象的具體屬性
export interface PersonInter {id: string,name: string,age: number,x?: number /* x是可選屬性, 該類型中可以有該屬性, 也可以無該屬性 */
}// 一個自定義類型
// export type Persons = Array<PersonInter>
export type Persons = PersonInter[] // 與上面等價
Person.vue
<template><div class="person"><ul><!-- 在模板中直接使用list, 不需要加props.list --><li v-for="p in list" :key="p.id">{{p.name}} -- {{p.age}}</li></ul></div>
</template><script lang="ts" setup name="Person">import {reactive, withDefaults} from 'vue'// 引入接口 或 自定義類型 的時候, 需要在前面加上type; import { type PersonInter, type Persons } from '@/types'// 不推薦的寫法, 但可用let personList:Persons = reactive([{id: 'a01', name: 'john', age:60}])// 推薦的寫法, 意為: personList2這個變量須符合 Persons 類型的規(guī)范let personList2 = reactive<Persons>([{id: 'a01', name: 'john', age:60}])// 推薦的寫法, 意為: personList3這個變量須符合 PersonInter[] 類型的規(guī)范let personList3 = reactive<PersonInter[]>([{id: 'a01', name: 'john', age:60}])// 只接收// 定義接收父組件傳過來的a屬性, 并賦值給props以便于訪問。并且defineProps只能使用1次/* let props = defineProps(['a', 'b'])// 在js代碼中使用props.a來訪問父組件傳過來的a屬性對應的值, 在模板中直接使用a來訪問父組件傳過來的a屬性對應的值console.log(props.a); */ // 接收 + 限制類型 + 限制必要性// (list2可不傳; list必須傳, 并且必須是Persons類型的)/* let props = defineProps<{list:Persons, list2?:Persons}>()console.log(props.list); */// 接收 + 限制類型 + 限制必要性 + 指定默認值// (list屬性可不傳, 如果沒有傳的話, 就是用下面默認定義的數(shù)據(jù))const props = withDefaults(defineProps<{list?: Persons}>(),{list: () => [{id:'A001',name:'張三',age:18}]})console.log(props.list);</script><style scoped></style>
3.14 生命周期
-
概念:
Vue
組件實例在創(chuàng)建時要經(jīng)歷一系列的初始化步驟,在此過程中Vue
會在合適的時機,調(diào)用特定的函數(shù),從而讓開發(fā)者有機會在特定階段運行自己的代碼,這些特定的函數(shù)統(tǒng)稱為:生命周期鉤子 -
規(guī)律:
生命周期整體分為四個階段,分別是:創(chuàng)建、掛載、更新、銷毀,每個階段都有兩個鉤子,一前一后。
-
Vue2
的生命周期創(chuàng)建階段:
beforeCreate
、created
掛載階段:
beforeMount
、mounted
更新階段:
beforeUpdate
、updated
銷毀階段:
beforeDestroy
、destroyed
-
Vue3
的生命周期創(chuàng)建階段:
setup
(替代了之前vue2中的beforeCreate、created)掛載階段:
onBeforeMount
、onMounted
更新階段:
onBeforeUpdate
、onUpdated
卸載階段:
onBeforeUnmount
、onUnmounted
(就對應vue2中的銷毀階段) -
常用的鉤子:
onMounted
(掛載完畢)、onUpdated
(更新完畢)、onBeforeUnmount
(卸載之前)
App.vue
<template><Person v-if="isShow"/>
</template><script lang="ts" setup name="App">import Person from './components/Person.vue'import {ref,onMounted} from 'vue'let isShow = ref(true)// 掛載完畢(先子組件掛載完畢, 再父掛載完畢)onMounted(()=>{console.log('父---掛載完畢')})</script>
Person.vue
<template><div class="person"><h2>當前求和為:{{ sum }}</h2><button @click="add">點我sum+1</button></div>
</template><script lang="ts" setup name="Person">import {ref,onBeforeMount, onMounted,onBeforeUpdate, onUpdated,onBeforeUnmount, onUnmounted } from 'vue'// 數(shù)據(jù)let sum = ref(0)// 方法function add(){sum.value += 1}// 創(chuàng)建(替代了之前vue2中的beforeCreate、created)console.log('創(chuàng)建')// 掛載前(這里面?zhèn)魅氲暮瘮?shù)由vue3幫我們調(diào)用, 這里只是將這個函數(shù)注冊進去)onBeforeMount(()=>{// console.log('掛載前')})// 掛載完畢onMounted(()=>{console.log('子---掛載完畢')})// 更新前onBeforeUpdate(()=>{// console.log('更新前')})// 更新完畢onUpdated(()=>{// console.log('更新完畢')})// 卸載前onBeforeUnmount(()=>{// console.log('卸載前')})// 卸載完畢onUnmounted(()=>{// console.log('卸載完畢')})
</script>
3.15 自定義hooks
未使用hooks前
App.vue
<template><Person />
</template><script lang="ts" setup name="App">
import Person from './components/Person.vue'
</script>
Person.vue
<template><div class="person"><h2>當前求和為:{{ sum }},放大10倍后:{{ bigSum }}</h2><button @click="add">點我sum+1</button><hr><img v-for="(dog, index) in dogList" :src="dog" :key="index"><button @click="getDog">再來一只小狗</button></div>
</template><script lang="ts" setup name="Person">import { ref, reactive, onMounted, computed } from 'vue'import axios from 'axios'// ---- 求和// 數(shù)據(jù)let sum = ref(0)let bigSum = computed(() => {return sum.value * 10})// 方法function add() {sum.value += 1}// 鉤子onMounted(() => {add()})// --- 發(fā)起請求獲取圖片// 數(shù)據(jù)let dogList = reactive(['https://images.dog.ceo/breeds/pembroke/n02113023_4373.jpg'])// 方法async function getDog() {try {let result = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')dogList.push(result.data.message)} catch (error) {alert(error)}}// 鉤子onMounted(() => {getDog()})</script><style scoped></style>
使用hooks
vue3本身就推薦使用組合式api,但是如果各種功能都放到setup里面,顯得就有點亂了,所以,使用hooks將單獨的功能所使用的各種數(shù)據(jù)、方法等抽離出去,當需要某個功能時,再引入進來。
hooks中不僅可以定義數(shù)據(jù),還可以使用聲明周期鉤子函數(shù),還可以寫計算屬性。
App.vue
<template><Person />
</template><script lang="ts" setup name="App">
import Person from './components/Person.vue'
</script>
Person.vue
<template><div class="person"><h2>當前求和為:{{ sum }},放大10倍后:{{ bigSum }}</h2><button @click="add">點我sum+1</button><hr><img v-for="(dog,index) in dogList" :src="dog" :key="index"><br><button @click="getDog">再來一只小狗</button></div>
</template><script lang="ts" setup name="Person">import useSum from '@/hooks/useSum'import useDog from '@/hooks/useDog'// 調(diào)用函數(shù)獲得數(shù)據(jù)const {sum,add,bigSum} = useSum()// 調(diào)用函數(shù)獲得數(shù)據(jù)const {dogList,getDog} = useDog()</script><style scoped></style>
hooks/useSum.ts
import { ref ,onMounted,computed} from 'vue'// 暴露此函數(shù)(默認暴露)
export default function () {// 數(shù)據(jù)let sum = ref(0)// 這里面也可以寫計算屬性的哦let bigSum = computed(()=>{return sum.value * 10})// 方法function add() {sum.value += 1}// 鉤子(hooks這里面也能寫鉤子的哦)onMounted(()=>{add()})// 給外部提供東西(要把東西放出去,讓外界使用)return {sum,add,bigSum}
}
hooks/useDog.ts
import {reactive,onMounted} from 'vue'
import axios from 'axios'export default function (){// 數(shù)據(jù)let dogList = reactive(['https://images.dog.ceo/breeds/pembroke/n02113023_4373.jpg'])// 方法async function getDog(){try {let result = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')dogList.push(result.data.message)} catch (error) {alert(error)}}// 鉤子(hooks這里面也能寫鉤子的哦)onMounted(()=>{getDog()})// 向外部提供東西return {dogList,getDog}
}
4.路由
4.1 路由的基本理解
當路由變化,路由器會監(jiān)聽到此變化,就會根據(jù)路由規(guī)則找到對應的組件,將這個組件展示在路由出口
4.2 基本切換效果
安裝vue-router
# 現(xiàn)在查看package.json,發(fā)現(xiàn)安裝的版本是【"vue-router": "^4.3.2"】
# 路由器是用來管理路由的, 并且當路徑變化時, 根據(jù)路由規(guī)則將對應的組件 展示在路由出口處
npm install vue-router
配置路由規(guī)則router/index.ts
// 創(chuàng)建一個路由器,并暴露出去// 第一步:引入createRouter
import {createRouter,createWebHistory} from 'vue-router'
// 引入一個一個可能要呈現(xiàn)組件
import Home from '@/components/Home.vue'
import News from '@/components/News.vue'
import About from '@/components/About.vue'// 第二步:創(chuàng)建路由器
const router = createRouter({history:createWebHistory(), //路由器的工作模式(稍后講解)routes:[ //一個一個的路由規(guī)則{path:'/home',component:Home},{path:'/news',component:News},{path:'/about',component:About},]
})// 暴露出去router
export default router
使用router路由管理器main.ts
// 引入createApp用于創(chuàng)建應用
import {createApp} from 'vue'
// 引入App根組件
import App from './App.vue'
// 引入路由器
import router from './router'// 創(chuàng)建一個應用
const app = createApp(App)
// 使用路由器
app.use(router)
// 掛載整個應用到app容器中
app.mount('#app')
路由展示區(qū)App.vue
<template><div class="app"><h2 class="title">Vue路由測試</h2><!-- 導航區(qū), 使用<router-link>標簽來切換路由路徑 --><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ū) , 使用<Router-view>標簽作為路由出口 --><div class="main-content"><RouterView></RouterView></div></div>
</template><script lang="ts" setup name="App">import {RouterView,RouterLink} from 'vue-router'</script><style>/* App */.title {text-align: center;word-spacing: 5px;margin: 30px 0;height: 70px;line-height: 70px;background-image: linear-gradient(45deg, gray, white);border-radius: 10px;box-shadow: 0 0 2px;font-size: 30px;}.navigate {display: flex;justify-content: space-around;margin: 0 100px;}.navigate a {display: block;text-align: center;width: 90px;height: 40px;line-height: 40px;border-radius: 10px;background-color: gray;text-decoration: none;color: white;font-size: 18px;letter-spacing: 5px;}.navigate a.active {background-color: #64967E;color: #ffc268;font-weight: 900;text-shadow: 0 0 1px black;font-family: 微軟雅黑;}.main-content {margin: 0 auto;margin-top: 30px;border-radius: 10px;width: 90%;height: 400px;border: 1px solid;}
</style>
路由組件
Home.vue
<template><div class="home"><img src="http://www.atguigu.com/images/index_new/logo.png" alt=""></div>
</template><script setup lang="ts" name="Home"></script><style scoped>.home {display: flex;justify-content: center;align-items: center;height: 100%;}
</style>
New.vue
<template><div class="news"><ul><li><a href="#">新聞001</a></li><li><a href="#">新聞002</a></li><li><a href="#">新聞003</a></li><li><a href="#">新聞004</a></li></ul></div>
</template><script setup lang="ts" name="News"></script><style scoped>
/* 新聞 */
.news {padding: 0 20px;display: flex;justify-content: space-between;height: 100%;
}
.news ul {margin-top: 30px;list-style: none;padding-left: 10px;
}
.news li>a {font-size: 18px;line-height: 40px;text-decoration: none;color: #64967E;text-shadow: 0 0 1px rgb(0, 84, 0);
}
.news-content {width: 70%;height: 90%;border: 1px solid;margin-top: 20px;border-radius: 10px;
}
</style>
About.vue
<template><div class="about"><h2>大家好,歡迎來到尚硅谷直播間</h2></div>
</template><script setup lang="ts" name="About"></script><style scoped>
.about {display: flex;justify-content: center;align-items: center;height: 100%;color: rgb(85, 84, 84);font-size: 18px;
}
</style>
路由切換效果圖
4.3. 兩個注意點
1、路由組件通常存放在pages
或 views
文件夾,一般組件通常存放在components
文件夾。
2、通過點擊導航,視覺效果上“消失” 了的路由組件,默認是被卸載掉的,需要的時候再去掛載。
About.vue
當通過切換路由路徑的方式而控制About.vue組件的顯示和隱藏時,會分別執(zhí)行onMounted 和 onUnmounted 中定義的函數(shù)
<template><div class="about"><h2>大家好,歡迎來到尚硅谷直播間</h2></div></template><script setup lang="ts" name="About">import {onMounted,onUnmounted} from 'vue'// 掛載時執(zhí)行的函數(shù)onMounted(()=>{console.log('About組件掛載了')})// 卸載時執(zhí)行的函數(shù)onUnmounted(()=>{console.log('About組件卸載了')})
</script><style scoped>.about {display: flex;justify-content: center;align-items: center;height: 100%;color: rgb(85, 84, 84);font-size: 18px;}
</style>
4.4. 路由器工作模式
-
history
模式優(yōu)點:
URL
更加美觀,不帶有#
,更接近傳統(tǒng)的網(wǎng)站URL
。缺點:后期項目上線,需要服務端配合處理路徑問題,否則刷新會有
404
錯誤。const router = createRouter({history:createWebHistory(), //history模式/******/ })
-
hash
模式優(yōu)點:兼容性更好,因為不需要服務器端處理路徑。
缺點:
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ī)則命名:
// 創(chuàng)建一個路由器,并暴露出去// 第一步:引入createRouter
import {createRouter,createWebHistory,createWebHashHistory} from 'vue-router'
// 引入一個一個可能要呈現(xiàn)組件
import Home from '@/pages/Home.vue'
import News from '@/pages/News.vue'
import About from '@/pages/About.vue'// 第二步:創(chuàng)建路由器
const router = createRouter({history:createWebHashHistory(), //路由器的工作模式(稍后講解)routes:[ //一個一個的路由規(guī)則{name:'zhuye',path:'/home',component:Home},{name:'xinwen',path:'/news',component:News},{name:'guanyu',path:'/about',component:About},]
})// 暴露出去router
export default router
跳轉(zhuǎn)路由:
<template><div class="app"><Header/><!-- 導航區(qū) --><div class="navigate"><!--簡化前:需要寫完整的路徑(to的字符串寫法) --><RouterLink to="/home" active-class="active">首頁</RouterLink><!--簡化后:直接通過路由規(guī)則中定義的路由的名字(route的name屬性)跳轉(zhuǎn)(to的對象寫法配合name屬性) --><RouterLink :to="{name:'xinwen'}" active-class="active">新聞</RouterLink><RouterLink :to="{path:'/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 {RouterView,RouterLink} from 'vue-router'import Header from './components/Header.vue'</script>
4.7 嵌套路由
main.ts
// 引入createApp用于創(chuàng)建應用
import {createApp} from 'vue'
// 引入App根組件
import App from './App.vue'
// 引入路由器
import router from './router'// 創(chuàng)建一個應用
const app = createApp(App)
// 使用路由器
app.use(router)
// 掛載整個應用到app容器中
app.mount('#app')
router/index.ts
當訪問/news/detail時,先根據(jù)路由規(guī)則匹配到News組件,這個News組件應該要展示在App.vue中的路由出口處,然后匹配到子級路由找到Detail.vue,然后將Detail.vue組件展示在News組件的路由出口處。
// 創(chuàng)建一個路由器,并暴露出去// 第一步:引入createRouter
import {createRouter,createWebHistory,createWebHashHistory} from 'vue-router'
// 引入一個一個可能要呈現(xiàn)組件
import Home from '@/pages/Home.vue'
import News from '@/pages/News.vue'
import About from '@/pages/About.vue'
import Detail from '@/pages/Detail.vue'// 第二步:創(chuàng)建路由器
const router = createRouter({history:createWebHistory(), //路由器的工作模式(稍后講解)routes:[ //一個一個的路由規(guī)則{name:'zhuye',path:'/home',component:Home},{name:'xinwen',path:'/news',component:News,children:[{path:'detail',component:Detail}]},{name:'guanyu',path:'/about',component:About},]
})// 暴露出去router
export default router
App.vue
在App.vue中有1個路由出口(一級路由出口)
<template><div class="app"><Header/><!-- 導航區(qū) --><div class="navigate"><RouterLink to="/home" active-class="active">首頁</RouterLink><RouterLink :to="{name:'xinwen'}" active-class="active">新聞</RouterLink><RouterLink :to="{path:'/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 {RouterView,RouterLink} from 'vue-router'import Header from './components/Header.vue'</script><style>/* App */.navigate {display: flex;justify-content: space-around;margin: 0 100px;}.navigate a {display: block;text-align: center;width: 90px;height: 40px;line-height: 40px;border-radius: 10px;background-color: gray;text-decoration: none;color: white;font-size: 18px;letter-spacing: 5px;}.navigate a.active {background-color: #64967E;color: #ffc268;font-weight: 900;text-shadow: 0 0 1px black;font-family: 微軟雅黑;}.main-content {margin: 0 auto;margin-top: 30px;border-radius: 10px;width: 90%;height: 400px;border: 1px solid;}
</style>
News.vue
在News.vue中有1個子級路由出口
<template><div class="news"><!-- 導航區(qū) --><ul><li v-for="news in newsList" :key="news.id"><RouterLink to="/news/detail">{{news.title}}</RouterLink></li></ul><!-- 展示區(qū) --><div class="news-content"><RouterView></RouterView></div></div>
</template><script setup lang="ts" name="News">import {reactive} from 'vue'import {RouterView,RouterLink} from 'vue-router'const newsList = reactive([{id:'asfdtrfay01',title:'很好的抗癌食物',content:'西藍花'},{id:'asfdtrfay02',title:'如何一夜暴富',content:'學IT'},{id:'asfdtrfay03',title:'震驚,萬萬沒想到',content:'明天是周一'},{id:'asfdtrfay04',title:'好消息!好消息!',content:'快過年了'}])</script><style scoped>
/* 新聞 */
.news {padding: 0 20px;display: flex;justify-content: space-between;height: 100%;
}
.news ul {margin-top: 30px;list-style: none;padding-left: 10px;
}
.news li>a {font-size: 18px;line-height: 40px;text-decoration: none;color: #64967E;text-shadow: 0 0 1px rgb(0, 84, 0);
}
.news-content {width: 70%;height: 90%;border: 1px solid;margin-top: 20px;border-radius: 10px;
}
</style>
Detail.vue
<template><ul class="news-list"><li>編號:xxx</li><li>標題:xxx</li><li>內(nèi)容:xxx</li></ul>
</template><script setup lang="ts" name="About"></script><style scoped>.news-list {list-style: none;padding-left: 20px;}.news-list>li {line-height: 30px;}
</style>
效果
可以看到在App.vue中有1個路由出口,在News.vue中也有1個路由出口
4.8 路由傳參
query參數(shù)
1.定義路由規(guī)則
const router = createRouter({history:createWebHistory(), //路由器的工作模式(稍后講解)routes:[ //一個一個的路由規(guī)則{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}]
})
2.傳遞參數(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>
3.接收參數(shù):
import {useRoute} from 'vue-router'
import {toRefs} from 'vue' const route = useRoute()// 從1個響應式對象直接解構(gòu)屬性(route是響應式對象),會丟失響應式
// 然后試圖在模板中使用此query, 發(fā)現(xiàn)點擊不同的新聞時數(shù)據(jù)沒有變化, 因為在解構(gòu)時這里已經(jīng)丟失了響應式了
// 應該使用toRefs
// const {query} = route // 應該如下使用toRefs
// 然后在模板中使用, 發(fā)現(xiàn)點擊不同的新聞時, 數(shù)據(jù)有了變化
const {query} = toRefs(route)// 打印query參數(shù)
console.log(route.query)
params參數(shù)
- 定義路由規(guī)則,并定義路由路徑params參數(shù)
const router = createRouter({history:createWebHistory(), //路由器的工作模式(稍后講解)routes:[ //一個一個的路由規(guī)則{name:'zhuye',path:'/home',component:Home},{name:'xinwen',path:'/news',component:News,children:[{name:'xiang',// 添加路徑參數(shù)來占位path:'detail/:id/:title/:content?', // 這里加個問號的意思是可傳可不傳, 否則必須傳component:Detail}]},{name:'guanyu',path:'/about',component:About}]
})
- 傳遞參數(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), 注意這里不能用path, 并且下面的params的屬性對應的值不能是對象或數(shù)組params:{id:news.id,title:news.title,content:news.title}}"
>{{news.title}}
</RouterLink>
- 接收參數(shù):
// useRoute是hooks鉤子
import {useRoute} from 'vue-router'const route = useRoute()// 從1個響應式對象直接解構(gòu)屬性(route是響應式對象),會丟失響應式
// 然后試圖在模板中使用此query, 發(fā)現(xiàn)點擊不同的新聞時數(shù)據(jù)沒有變化, 因為在解構(gòu)時這里已經(jīng)丟失了響應式了
// 應該使用toRefs
// const {params} = route // 應該如下使用toRefs
// 然后在模板中使用, 發(fā)現(xiàn)點擊不同的新聞時, 數(shù)據(jù)有了變化
const {params} = toRefs(route)// 打印params參數(shù)
console.log(route.params)
備注1:傳遞
params
參數(shù)時,若使用to
的對象寫法,必須使用name
配置項,不能用path
。備注2:傳遞
params
參數(shù)時,需要提前在規(guī)則中占位。
4.9 路由的props配置
作用:讓路由組件更方便的收到參數(shù)(可以將路由參數(shù)作為props
傳給組件)
{name:'xiang',path:'detail/:id/:title/:content',component:Detail,// 第一種寫法:將路由收到的【所有params參數(shù)】作為props傳給路由組件// props的布爾值寫法,作用:把收到了每一組params參數(shù),作為props傳給Detail組件,// (類似于: <Detail :id='xx' :title='xx' :content='xx' />)// 這樣在Detail組件中通過defineProps(['id','title','content'])聲明屬性, // 然后在模板中直接使用id,title,content就可以訪問這些屬性了// props:true// 第二種寫法:函數(shù)寫法,可以自己決定將什么作為props給路由組件// props的函數(shù)寫法,作用:把返回的對象中每一組key-value作為props傳給Detail組件// 這里的形參可以不叫route, 換成其它任何名字都代表路由對象// 這樣在Detail組件中通過defineProps(['k'])聲明屬性, // 然后在模板中直接使用k就可以訪問k屬性對應的值了, route.query中的屬性也是一樣props(route){return {...route.query, k:'v'}}// 第三種寫法:對象寫法,可以自己決定將什么作為props給路由組件// props的對象寫法,作用:把對象中的每一組key-value作為props傳給Detail組件// props:{a:1,b:2,c:3}, // 以上寫法請注意, 都是在指定Detail作為路由組件展示在路由出口時, 給該【路由組件】傳遞的props, // 注意與直接使用<Detail/>標簽的形式的【一般組件】區(qū)別開來
}
4.10 replace屬性
-
作用:控制路由跳轉(zhuǎn)時操作瀏覽器歷史記錄的模式。
-
瀏覽器的歷史記錄有兩種寫入方式:分別為
push
和replace
:push
是追加歷史記錄(默認值)。replace
是替換當前記錄。
-
開啟
replace
模式:<RouterLink replace to='/news/detail/1'>News</RouterLink>
示例
<template><div class="app"><Header/><!-- 導航區(qū) --><div class="navigate"><RouterLink to="/home" active-class="active">首頁</RouterLink><RouterLink replace :to="{name:'xinwen'}" active-class="active">新聞</RouterLink><RouterLink replace :to="{path:'/about'}" active-class="active">關(guān)于</RouterLink></div><!-- 展示區(qū) --><div class="main-content"><RouterView></RouterView></div></div>
</template>
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)// <RouterLink to=''/>標簽中的to屬性能怎么寫, 那么router.push(..)中的參數(shù)就能怎么寫
console.log(router.push)
console.log(router.replace)
示例
<template><div class="news"><!-- 導航區(qū) --><ul><li v-for="news in newsList" :key="news.id"><button @click="showNewsDetail(news)">查看新聞</button><RouterLink :to="{name:'xiang',query:{id:news.id,title:news.title,content:news.content}}">{{news.title}}</RouterLink></li></ul><!-- 展示區(qū) --><div class="news-content"><RouterView></RouterView></div></div>
</template><script setup lang="ts" name="News">import {reactive} from 'vue'import {RouterView,RouterLink,useRouter} from 'vue-router'const newsList = reactive([{id:'asfdtrfay01',title:'很好的抗癌食物',content:'西藍花'},{id:'asfdtrfay02',title:'如何一夜暴富',content:'學IT'},{id:'asfdtrfay03',title:'震驚,萬萬沒想到',content:'明天是周一'},{id:'asfdtrfay04',title:'好消息!好消息!',content:'快過年了'}])const router = useRouter()interface NewsInter {id:string,title:string,content:string}function showNewsDetail(news:NewsInter){router.replace({name:'xiang',query:{id:news.id,title:news.title,content:news.content}})}</script><style scoped>
/* 新聞 */
.news {padding: 0 20px;display: flex;justify-content: space-between;height: 100%;
}
.news ul {margin-top: 30px;/* list-style: none; */padding-left: 10px;
}
.news li::marker {color: #64967E;
}
.news li>a {font-size: 18px;line-height: 40px;text-decoration: none;color: #64967E;text-shadow: 0 0 1px rgb(0, 84, 0);
}
.news-content {width: 70%;height: 90%;border: 1px solid;margin-top: 20px;border-radius: 10px;
}
</style>
4.12 重定向
-
作用:將特定的路徑,重新定向到已有路由。
-
具體編碼:
{path:'/',redirect:'/about' }
示例
// 創(chuàng)建一個路由器,并暴露出去// 第一步:引入createRouter
import {createRouter,createWebHistory,createWebHashHistory} from 'vue-router'// 引入一個一個可能要呈現(xiàn)組件
import Home from '@/pages/Home.vue'
import News from '@/pages/News.vue'
import About from '@/pages/About.vue'
import Detail from '@/pages/Detail.vue'// 第二步:創(chuàng)建路由器
const router = createRouter({history:createWebHistory(), //路由器的工作模式(稍后講解)routes:[ //一個一個的路由規(guī)則{name:'zhuye',path:'/home',component:Home},{name:'xinwen',path:'/news',component:News,children:[{name:'xiang',path:'detail',component:Detail,props(route){return route.query}}]},{name:'guanyu',path:'/about',component:About},{path:'/',// 使用重定向, 當用戶訪問/時, 跳轉(zhuǎn)到/home// 即: 讓指定的路徑重新定位到另一個路徑redirect:'/home'}]
})// 暴露出去router
export default router
5. pinia
5.1 準備一個效果

main.ts
// 引入createApp用于創(chuàng)建應用
import {createApp} from 'vue'// 引入App根組件
import App from './App.vue'// 創(chuàng)建一個應用
const app = createApp(App)// 掛載整個應用到app容器中
app.mount('#app')
App.vue
<template><Count/><br><LoveTalk/>
</template><script setup lang="ts" name="App">import Count from './components/Count.vue'import LoveTalk from './components/LoveTalk.vue'
</script>
Count.vue
<template><div class="count"><h2>當前求和為:{{ sum }}</h2><!-- 如果不寫.number, 那么綁定所獲取的值是字符串 --><!-- 當然也可以這樣使用v-bind來綁定, 如: <option :value="1">1</option> --><select v-model.number="n"><option value="1">1</option><option value="2">2</option><option value="3">3</option></select><button @click="add">加</button><button @click="minus">減</button></div>
</template><script setup lang="ts" name="Count">import { ref } from "vue";// 數(shù)據(jù)let sum = ref(1) // 當前求和let n = ref(1) // 用戶選擇的數(shù)字// 方法function add(){sum.value += n.value}function minus(){sum.value -= n.value}
</script><style scoped>.count {background-color: skyblue;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}select,button {margin: 0 5px;height: 25px;}
</style>
LoveTalk.vue
<template><div class="talk"><button @click="getLoveTalk">獲取一句土味情話</button><ul><li v-for="talk in talkList" :key="talk.id">{{talk.title}}</li></ul></div>
</template><script setup lang="ts" name="LoveTalk">import {reactive} from 'vue'import axios from "axios";import {nanoid} from 'nanoid'// 數(shù)據(jù)let talkList = reactive([{id:'ftrfasdf01',title:'今天你有點怪,哪里怪?怪好看的!'},{id:'ftrfasdf02',title:'草莓、藍莓、蔓越莓,今天想我了沒?'},{id:'ftrfasdf03',title:'心里給你留了一塊地,我的死心塌地'}])// 方法async function getLoveTalk(){// 發(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)}
</script><style scoped>.talk {background-color: orange;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}
</style>
5.2 搭建 pinia 環(huán)境
使用步驟
第一步:npm install pinia
(此處安裝的版本是:“pinia”: “^2.1.7”,)
第二步:操作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
選項

5.3 存儲+讀取數(shù)據(jù)
-
Store
是一個保存:狀態(tài)、業(yè)務邏輯 的實體,每個組件都可以讀取、寫入它。 -
它有三個概念:
state
、getter
、action
,相當于組件中的:data
、computed
和methods
。
store/count.ts
import { defineStore } from 'pinia'// defineStore返回的值的命名 格式為: use{文件名}Store
export const useCountStore = defineStore('count', /* 建議這里的名字與文件名保持一直, 首字母小寫 */{// 真正存儲數(shù)據(jù)的地方state() { // 這個只能寫成1個函數(shù)return {sum: 6}}
})
store/loveTalk.ts
import {defineStore} from 'pinia'export const useTalkStore = defineStore('talk',{// 真正存儲數(shù)據(jù)的地方state(){return {talkList:[{id:'ftrfasdf01',title:'今天你有點怪,哪里怪?怪好看的!'},{id:'ftrfasdf02',title:'草莓、藍莓、蔓越莓,今天想我了沒?'},{id:'ftrfasdf03',title:'心里給你留了一塊地,我的死心塌地'}]}}
})
Count.vue
<template><div class="count"><!-- 直接使用countStore --><h2>當前求和為:{{ countStore.sum }}</h2><select v-model.number="n"><option value="1">1</option><option value="2">2</option><option value="3">3</option></select><button @click="add">加</button><button @click="minus">減</button></div>
</template><script setup lang="ts" name="Count">import { ref, reactive } from "vue";import { useCountStore } from '@/store/count'const countStore = useCountStore()// 以下兩種方式都可以拿到state中的數(shù)據(jù)// console.log('@@@',countStore.sum) // 注意: 這里后面不要寫.value哦, 因為會自動拆包// console.log('@@@',countStore.$state.sum) // 也可以通過$state拿到sum/* let obj = reactive({a:1,b:2,c:ref(3)})let x = ref(9)console.log(obj.a)console.log(obj.b)console.log(obj.c) // 注意, 這里最后面就不用.value了*/// 數(shù)據(jù)let n = ref(1) // 用戶選擇的數(shù)字// 方法function add() {}function minus() {}
</script><style scoped>.count {background-color: skyblue;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}select,button {margin: 0 5px;height: 25px;}
</style>
LoveTalk.vue
<template><div class="talk"><button @click="getLoveTalk">獲取一句土味情話</button><ul><li v-for="talk in talkStore.talkList" :key="talk.id">{{talk.title}}</li></ul></div>
</template><script setup lang="ts" name="LoveTalk">import {reactive} from 'vue'import axios from "axios";import {nanoid} from 'nanoid'import {useTalkStore} from '@/store/loveTalk'const talkStore = useTalkStore()// 方法async function getLoveTalk(){// 發(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)}
</script><style scoped>.talk {background-color: orange;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}
</style>
App.vue
<template><Count/><br><LoveTalk/>
</template><script setup lang="ts" name="App">import Count from './components/Count.vue'import LoveTalk from './components/LoveTalk.vue'
</script>
main.ts
import {createApp} from 'vue'
import App from './App.vue'
// 第一步:引入pinia
import {createPinia} from 'pinia'const app = createApp(App)
// 第二步:創(chuàng)建pinia
const pinia = createPinia()
// 第三步:安裝pinia
app.use(pinia)
app.mount('#app')
5.4 修改數(shù)據(jù)(三種方式)
第一種方式
count.ts
import {defineStore} from 'pinia'export const useCountStore = defineStore('count',{// 真正存儲數(shù)據(jù)的地方state(){return {sum:6,school:'atguigu',address:'宏??萍紙@'}}
})
Count.vue
<template><div class="count"><h2>當前求和為:{{ countStore.sum }}</h2><button @click="add">加</button></div>
</template><script setup lang="ts" name="Count">import { ref, reactive } from "vue";// 引入useCountStoreimport { useCountStore } from '@/store/count'// 使用useCountStore,得到一個專門保存count相關(guān)的storeconst countStore = useCountStore()// 數(shù)據(jù)let n = ref(1) // 用戶選擇的數(shù)字// 方法function add() {// 第一種修改方式, 直接拿到countStore去改, 注意: 這和vuex不同, vuex是不能直接修改的countStore.sum += 1countStore.school = '尚硅谷'countStore.address = '北京'}
</script><style scoped>.count {background-color: skyblue;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}select,button {margin: 0 5px;height: 25px;}
</style>
第二種方式
count.ts
import {defineStore} from 'pinia'export const useCountStore = defineStore('count',{// 真正存儲數(shù)據(jù)的地方state(){return {sum:6,school:'atguigu',address:'宏福科技園'}}
})
Count.vue
<template><div class="count"><h2>當前求和為:{{ countStore.sum }}</h2><h3>歡迎來到:{{ countStore.school }},坐落于:{{ countStore.address }}</h3><select v-model.number="n"><option value="1">1</option><option value="2">2</option><option value="3">3</option></select><button @click="add">加</button><button @click="minus">減</button></div>
</template><script setup lang="ts" name="Count">import { ref,reactive } from "vue";// 引入useCountStoreimport {useCountStore} from '@/store/count'// 使用useCountStore,得到一個專門保存count相關(guān)的storeconst countStore = useCountStore()// 數(shù)據(jù)let n = ref(1) // 用戶選擇的數(shù)字// 方法function add(){// 第二種修改方式(如果很多數(shù)據(jù)都要統(tǒng)一一次性發(fā)生變化,推薦使用$patch)countStore.$patch({sum:888,school:'尚硅谷',address:'北京'})}function minus(){}
</script><style scoped>.count {background-color: skyblue;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}select,button {margin: 0 5px;height: 25px;}
</style>
第三種方式
count.ts
import {defineStore} from 'pinia'export const useCountStore = defineStore('count',{// actions里面放置的是一個一個的方法,用于響應組件中的“動作”// (使用actions的意義在于可以將對組件共享數(shù)據(jù)統(tǒng)一操作的邏輯抽取放到這里)actions:{increment(value){ // value是調(diào)用方傳過來的值console.log('increment被調(diào)用了',value)if( this.sum < 10){// 修改數(shù)據(jù)(this是當前的store)this.sum += value}}},// 真正存儲數(shù)據(jù)的地方state(){return {sum:6,school:'atguigu',address:'宏??萍紙@'}}
})
Count.vue
<template><div class="count"><h2>當前求和為:{{ countStore.sum }}</h2><h3>歡迎來到:{{ countStore.school }},坐落于:{{ countStore.address }}</h3><select v-model.number="n"><option value="1">1</option><option value="2">2</option><option value="3">3</option></select><button @click="add">加</button><button @click="minus">減</button></div>
</template><script setup lang="ts" name="Count">import { ref,reactive } from "vue";// 引入useCountStoreimport {useCountStore} from '@/store/count'// 使用useCountStore,得到一個專門保存count相關(guān)的storeconst countStore = useCountStore()// 數(shù)據(jù)let n = ref(1) // 用戶選擇的數(shù)字// 方法function add(){// 第三種修改方式(直接調(diào)用count.ts中定義的actions方法)const result = countStore.increment(n.value)console.log('result', result); // result undefined}function minus(){}
</script><style scoped>.count {background-color: skyblue;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}select,button {margin: 0 5px;height: 25px;}
</style>
5.5 storeToRefs用法
- 借助
storeToRefs
將store
中的數(shù)據(jù)轉(zhuǎn)為ref
對象,方便在模板中使用。 - 注意:
pinia
提供的storeToRefs
只會將數(shù)據(jù)做轉(zhuǎn)換,而Vue
的toRefs
會轉(zhuǎn)換store
中數(shù)據(jù)(雖然能實現(xiàn)功能,單不建議使用哦)。
LoveTalk.ts
import {defineStore} from 'pinia'
import axios from 'axios'
import {nanoid} from 'nanoid'export const useTalkStore = defineStore('talk',{actions:{async 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ù)組中this.talkList.unshift(obj)}},// 真正存儲數(shù)據(jù)的地方state(){return {talkList:[{id:'ftrfasdf01',title:'今天你有點怪,哪里怪?怪好看的!'},{id:'ftrfasdf02',title:'草莓、藍莓、蔓越莓,今天想我了沒?'},{id:'ftrfasdf03',title:'心里給你留了一塊地,我的死心塌地'}]}}
})
LoveTask.vue
<template><div class="talk"><button @click="getLoveTalk">獲取一句土味情話</button><ul><li v-for="talk in talkList" :key="talk.id">{{talk.title}}</li></ul></div>
</template><script setup lang="ts" name="LoveTalk">import {useTalkStore} from '@/store/loveTalk'import { storeToRefs } from "pinia";const talkStore = useTalkStore()// 這里如果直接這樣解構(gòu)寫: const {talkList} = taskStore; 那么此時這里的talkList就已經(jīng)丟失了響應式// 這里雖然也可以寫: const {talkList} = toRefs(taskStore); 雖然可以維持talkList的響應式, 但代價過大,// (toRefs會把talkStore中的全部數(shù)據(jù)包括函數(shù),state啥的都給包了一遍)// 所以最好使用storeToRefs, 因為storeToRefs只會關(guān)注sotre中數(shù)據(jù),不會對方法進行ref包裹const {talkList} = storeToRefs(talkStore)// 方法function getLoveTalk(){talkStore.getATalk()}
</script><style scoped>.talk {background-color: orange;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}
</style>
count.ts
import {defineStore} from 'pinia'export const useCountStore = defineStore('count',{// actions里面放置的是一個一個的方法,用于響應組件中的“動作”actions:{increment(value:number){console.log('increment被調(diào)用了',value)if( this.sum < 10){// 修改數(shù)據(jù)(this是當前的store)this.sum += value}}},// 真正存儲數(shù)據(jù)的地方state(){return {sum:1,school:'atguigu',address:'宏福科技園'}}
})
Count.vue
<template><div class="count"><h2>當前求和為:{{ sum }}</h2><h3>歡迎來到:{{ school }},坐落于:{{ address }}</h3><select v-model.number="n"><option value="1">1</option><option value="2">2</option><option value="3">3</option></select><button @click="add">加</button><button @click="minus">減</button></div>
</template><script setup lang="ts" name="Count">import { ref,reactive,toRefs } from "vue";import {storeToRefs} from 'pinia'// 引入useCountStoreimport {useCountStore} from '@/store/count'// 使用useCountStore,得到一個專門保存count相關(guān)的storeconst countStore = useCountStore()// storeToRefs只會關(guān)注sotre中數(shù)據(jù),不會對方法進行ref包裹const {sum,school,address} = storeToRefs(countStore)// console.log('!!!!!',storeToRefs(countStore))// 數(shù)據(jù)let n = ref(1) // 用戶選擇的數(shù)字// 方法function add(){countStore.increment(n.value)}function minus(){countStore.sum -= n.value}
</script><style scoped>.count {background-color: skyblue;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}select,button {margin: 0 5px;height: 25px;}
</style>
5.6 getters用法
概念:當state
中的數(shù)據(jù),需要經(jīng)過處理后再使用時,可以使用getters
配置。
count.ts
import {defineStore} from 'pinia'export const useCountStore = defineStore('count',{// actions里面放置的是一個一個的方法,用于響應組件中的“動作”actions:{increment(value:number){console.log('increment被調(diào)用了',value)if( this.sum < 10){// 修改數(shù)據(jù)(this是當前的store)this.sum += value}}},// 真正存儲數(shù)據(jù)的地方state(){return {sum:3,school:'atguigu',address:'宏??萍紙@'}},getters:{bigSum:state => state.sum * 10,upperSchool():string{return this.school.toUpperCase()}}
})
Count.vue
<template><div class="count"><h2>當前求和為:{{ sum }},放大10倍后:{{ bigSum }}</h2><h3>歡迎來到:{{ school }},坐落于:{{ address }},大寫:{{ upperSchool }}</h3><select v-model.number="n"><option value="1">1</option><option value="2">2</option><option value="3">3</option></select><button @click="add">加</button><button @click="minus">減</button></div>
</template><script setup lang="ts" name="Count">import { ref,reactive,toRefs } from "vue";import {storeToRefs} from 'pinia'// 引入useCountStoreimport {useCountStore} from '@/store/count'// 使用useCountStore,得到一個專門保存count相關(guān)的storeconst countStore = useCountStore()// storeToRefs只會關(guān)注sotre中數(shù)據(jù),不會對方法進行ref包裹, 并且同時維持解構(gòu)屬性結(jié)果的響應式// (可以直接解構(gòu)出state和getters中定義的數(shù)據(jù))const {sum,school,address,bigSum,upperSchool} = storeToRefs(countStore)// console.log('!!!!!',storeToRefs(countStore))// 數(shù)據(jù)let n = ref(1) // 用戶選擇的數(shù)字// 方法function add(){countStore.increment(n.value)}function minus(){countStore.sum -= n.value}
</script><style scoped>.count {background-color: skyblue;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}select,button {margin: 0 5px;height: 25px;}
</style>
5.7 $subscribe的使用
通過 store 的 $subscribe()
方法偵聽 state
及其變化
loveTalk.ts
import {defineStore} from 'pinia'
import axios from 'axios'
import {nanoid} from 'nanoid'export const useTalkStore = defineStore('talk',{actions:{async 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ù)組中this.talkList.unshift(obj)}},// 真正存儲數(shù)據(jù)的地方state(){return {talkList:JSON.parse(localStorage.getItem('talkList') as string) || []}}
})
LoveTalk.vue
<template><div class="talk"><button @click="getLoveTalk">獲取一句土味情話</button><ul><li v-for="talk in talkList" :key="talk.id">{{ talk.title }}</li></ul></div>
</template><script setup lang="ts" name="LoveTalk">import { useTalkStore } from '@/store/loveTalk'import { storeToRefs } from "pinia";const talkStore = useTalkStore()const { talkList } = storeToRefs(talkStore)talkStore.$subscribe((mutate, state) => {// 注意: 箭頭函數(shù)中沒有thisconsole.log('talkStore里面保存的數(shù)據(jù)發(fā)生了變化', mutate, state)// 實現(xiàn)頁面刷新時, 這里的talkList不丟失, 因為在loveTalk.ts中會取localStorage中讀取talkList數(shù)據(jù)localStorage.setItem('talkList', JSON.stringify(state.talkList))})// 方法function getLoveTalk() {talkStore.getATalk()}
</script><style scoped>.talk {background-color: orange;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}
</style>
5.8 store組合式寫法
loveTalk.js
import {defineStore} from 'pinia'
import axios from 'axios'
import {nanoid} from 'nanoid'/* export const useTalkStore = defineStore('talk',{actions:{async 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ù)組中this.talkList.unshift(obj)}},// 真正存儲數(shù)據(jù)的地方state(){return {talkList:JSON.parse(localStorage.getItem('talkList') as string) || []}}
})*/import {reactive} from 'vue'
export const useTalkStore = defineStore('talk',()=>{// talkList就是stateconst talkList = reactive(JSON.parse(localStorage.getItem('talkList') as string) || [])// getATalk函數(shù)相當于actionasync 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}
})
LoveTalk.vue
<template><div class="talk"><button @click="getLoveTalk">獲取一句土味情話</button><ul><li v-for="talk in talkList" :key="talk.id">{{talk.title}}</li></ul></div>
</template><script setup lang="ts" name="LoveTalk">import {useTalkStore} from '@/store/loveTalk'import { storeToRefs } from "pinia";const talkStore = useTalkStore()const {talkList} = storeToRefs(talkStore)talkStore.$subscribe((mutate,state)=>{console.log('talkStore里面保存的數(shù)據(jù)發(fā)生了變化',mutate,state)localStorage.setItem('talkList',JSON.stringify(state.talkList))})// 方法function getLoveTalk(){talkStore.getATalk()}
</script><style scoped>.talk {background-color: orange;padding: 10px;border-radius: 10px;box-shadow: 0 0 10px;}
</style>
6. 組件通信
6.1 props
概述:props
是使用頻率最高的一種通信方式,常用與 :父 ? 子。
- 若 父傳子:屬性值是非函數(shù)。
- 若 子傳父:屬性值是函數(shù)。
這種不適合父子孫中父給孫組件傳遞數(shù)據(jù),或者兄弟組件也可以找到同1個父組件來實現(xiàn)兄弟組件通信
Father.vue
<template><div class="father"><h3>父組件</h3><h4>汽車:{{ car }}</h4><h4 v-show="toy">子給的玩具:{{ toy }}</h4><Child :car="car" :sendToy="getToy" /></div>
</template><script setup lang="ts" name="Father">import Child from './Child.vue'import { ref } from 'vue'// 數(shù)據(jù)let car = ref('奔馳')let toy = ref('')// 方法function getToy(value: string) {toy.value = value}</script><style scoped>.father {background-color: rgb(165, 164, 164);padding: 20px;border-radius: 10px;}
</style>
Child.vue
<template><div class="child"><h3>子組件</h3><h4>玩具:{{ toy }}</h4><h4>父給的車:{{ car }}</h4><button @click="sendToy(toy)">把玩具給父親</button></div>
</template><script setup lang="ts" name="Child">import { ref } from 'vue'// 數(shù)據(jù)let toy = ref('奧特曼')// 聲明接收propsdefineProps(['car', 'sendToy'])</script><style scoped>.child {background-color: skyblue;padding: 10px;box-shadow: 0 0 10px black;border-radius: 10px;}
</style>
6.2 自定義事件
Father.vue
<template><div class="father"><h3>父組件</h3><h4 v-show="toy">子給的玩具:{{ toy }}</h4><!-- 給子組件Child綁定事件 --><Child @send-toy="saveToy" /></div>
</template><script setup lang="ts" name="Father">import Child from './Child.vue'import { ref } from "vue";// 數(shù)據(jù)let toy = ref('')// 用于保存?zhèn)鬟f過來的玩具function saveToy(value: string,e:any) {console.log('saveToy', value, e)toy.value = value}</script><style scoped>.father {background-color: rgb(165, 164, 164);padding: 20px;border-radius: 10px;}.father button {margin-right: 5px;}
</style>
Child.vue
<template><div class="child"><h3>子組件</h3><h4>玩具:{{ toy }}</h4><!-- 在模板中可以使用$event來代表事件對象 --><button @click="emit('send-toy', toy, $event)">測試</button></div>
</template><script setup lang="ts" name="Child">import { ref } from "vue";// 數(shù)據(jù)let toy = ref('奧特曼')// 聲明事件const emit = defineEmits(['send-toy'])</script><style scoped>.child {margin-top: 10px;background-color: rgb(76, 209, 76);padding: 10px;box-shadow: 0 0 10px black;border-radius: 10px;}
</style>
6.3 mitt
概述:與消息訂閱與發(fā)布(pubsub
)功能類似,可以實現(xiàn)任意組件間通信。
安裝mitt
,npm install mitt
,版本是:“mitt”: “^3.0.1”
emitter.ts
// 引入mitt
import mitt from 'mitt'// 調(diào)用mitt得到emitter,emitter能:綁定事件、觸發(fā)事件
const emitter = mitt()/*
// 綁定事件
emitter.on('test1',()=>{console.log('test1被調(diào)用了')
})
emitter.on('test2',()=>{console.log('test2被調(diào)用了')
})// 觸發(fā)事件
setInterval(() => {emitter.emit('test1')emitter.emit('test2')
}, 1000);setTimeout(() => {// emitter.off('test1')// emitter.off('test2')emitter.all.clear()
}, 3000);
*/// 暴露emitter
export default emitter
Father.vue
<template><div class="father"><h3>父組件</h3><Child1/><Child2/></div>
</template><script setup lang="ts" name="Father">import Child1 from './Child1.vue'import Child2 from './Child2.vue'
</script><style scoped>.father{background-color:rgb(165, 164, 164);padding: 20px;border-radius: 10px;}.father button{margin-left: 5px;}
</style>
Child1.vue
<template><div class="child1"><h3>子組件1</h3><h4>玩具:{{ toy }}</h4><button @click="emitter.emit('send-toy',toy)">玩具給弟弟</button></div>
</template><script setup lang="ts" name="Child1">import {ref} from 'vue'import emitter from '@/utils/emitter';// 數(shù)據(jù)let toy = ref('奧特曼')
</script><style scoped>.child1{margin-top: 50px;background-color: skyblue;padding: 10px;box-shadow: 0 0 10px black;border-radius: 10px;}.child1 button{margin-right: 10px;}
</style>
Child2.vue
<template><div class="child2"><h3>子組件2</h3><h4>電腦:{{ computer }}</h4><h4>哥哥給的玩具:{{ toy }}</h4></div>
</template><script setup lang="ts" name="Child2">import { ref, onUnmounted } from 'vue'import emitter from '@/utils/emitter';// 數(shù)據(jù)let computer = ref('聯(lián)想')let toy = ref('')// 給emitter綁定send-toy事件emitter.on('send-toy', (value: any) => {toy.value = value})// 在組件卸載時解綁send-toy事件onUnmounted(() => {emitter.off('send-toy')})
</script><style scoped>.child2 {margin-top: 50px;background-color: orange;padding: 10px;box-shadow: 0 0 10px black;border-radius: 10px;}
</style>
6.4 v-model
Father.vue
<template><div class="father"><h3>父組件</h3><h4>{{ username }}</h4><h4>{{ password }}</h4><!-- v-model用在html標簽上 --><!-- <input type="text" v-model="username"> --><!-- <input type="text" :value="username" @input="username = (<HTMLInputElement>$event.target).value"> --><!-- v-model用在組件標簽上 --><!-- <AtguiguInput v-model="username"/> --><!-- 上面這行等價于下面這行 --><!-- $event到底是啥? 啥時候能.target對于原生事件, $event就是事件對象 ===> 能.target對于自定義事件, $event就是觸發(fā)事件時, 所傳遞的數(shù)據(jù) ===> 不能.target--><!-- <AtguiguInput :modelValue="username" @update:modelValue="username = $event"/> --><!-- 修改modelValue --><AtguiguInput v-model:ming="username" v-model:mima="password"/></div>
</template><script setup lang="ts" name="Father">import { ref } from "vue";import AtguiguInput from './AtguiguInput.vue'// 數(shù)據(jù)let username = ref('zhansgan')let password = ref('123456')
</script><style scoped>.father {padding: 20px;background-color: rgb(165, 164, 164);border-radius: 10px;}
</style>
AtguiguInput.vue
<template><input type="text" :value="ming"@input="emit('update:ming',(<HTMLInputElement>$event.target).value)"><br><input type="text" :value="mima"@input="emit('update:mima',(<HTMLInputElement>$event.target).value)">
</template><script setup lang="ts" name="AtguiguInput">defineProps(['ming','mima'])const emit = defineEmits(['update:ming','update:mima'])</script><style scoped>input {border: 2px solid black;background-image: linear-gradient(45deg,red,yellow,green);height: 30px;font-size: 20px;color: white;}
</style>
6.5 $attrs
-
概述:
$attrs
用于實現(xiàn)**當前組件的父組件,向當前組件的子組件**通信(祖→孫)。 -
具體說明:
$attrs
是一個對象,包含所有父組件傳入的標簽屬性。注意:
$attrs
會自動排除props
中聲明的屬性(可以認為聲明過的props
被子組件自己“消費”了)(就是父組件給子組件通過標簽的屬性方式傳遞給子組件,子組件使用props的方式只接收了部分屬性,其它沒有接收的屬性可以通過子組件的$attrs來訪問)
Father.vue
<template><div class="father"><h3>父組件</h3><h4>a:{{a}}</h4><h4>b:{}</h4><h4>c:{{c}}</h4><h4>d:{vxwlu0yf4}</h4><!-- v-bind="{x:100,y:200}就等價: :x=100 :y=200 --><Child :a="a" :b="b" :c="c" :d="d" :e="e" 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)let e = ref(5)function updateA(value:number){a.value += value}
</script><style scoped>.father{background-color: rgb(165, 164, 164);padding: 20px;border-radius: 10px;}
</style>
Child.vue
<template><div class="child"><h3>子組件</h3><h4>{{ e }}</h4><!-- Father組件傳給Child組件的屬性,但是Father組件沒有使用props接收的屬性,就存在$attrs中 --><h4>{{ $attrs }}</h4><!-- Father組件傳給Child組件的屬性,但是Father組件沒有使用props接收的屬性,全部傳遞給GrandChild組件--><GrandChild v-bind="$attrs"/></div>
</template><script setup lang="ts" name="Child">import GrandChild from './GrandChild.vue'defineProps(['e'])
</script><style scoped>.child{margin-top: 20px;background-color: skyblue;padding: 20px;border-radius: 10px;box-shadow: 0 0 10px black;}
</style>
GrandChild.vue
<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><!-- Father組件通過Child組件的v-bind="$attr"將函數(shù)傳給GrandChild組件,這樣GrandChild組件就可以通過此函數(shù)傳遞數(shù)據(jù)給Father組件了 --><button @click="updateA(6)">點我將爺爺那的a更新</button></div>
</template><script setup lang="ts" name="GrandChild">// 接收Father組件傳遞過來并由Child組件通過v-bind="$attr"中轉(zhuǎn)過來的屬性defineProps(['a','b','c','d','x','y','updateA'])
</script><style scoped>.grand-child{margin-top: 20px;background-color: orange;padding: 20px;border-radius: 10px;box-shadow: 0 0 10px black;}
</style>
6.6 r e f s 、 refs、 refs、parent、proxy
-
概述:
$refs
用于 :父→子。$parent
用于:子→父。
-
原理如下:
屬性 說明 $refs
值為對象,包含所有被 ref
屬性標識的DOM
元素或組件實例。$parent
值為對象,當前組件的父組件實例對象。
Father.vue
<template><div class="father"><h3>父組件</h3><h4>房產(chǎn):{{ house }}</h4><button @click="changeToy">修改Child1的玩具</button><button @click="changeComputer">修改Child2的電腦</button><!-- 在模板中可以直接使用$refs --><button @click="getAllChild($refs)">讓所有孩子的書變多</button><button @click="getAllChild2()">讓c1孩子的書變多2</button><button @click="getAllChild3()">讓c1孩子的書變多3</button><Child1 ref="c1"/><Child2 ref="c2"/></div>
</template><script setup lang="ts" name="Father">import Child1 from './Child1.vue'import Child2 from './Child2.vue'import { ref,reactive } from "vue";import { getCurrentInstance } from 'vue';const proxy = getCurrentInstance()let c1 = ref()let c2 = ref()// 注意點:當訪問obj.c的時候,底層會自動讀取value屬性,因為c是在obj這個響應式對象中的/* let obj = reactive({a:1,b:2,c:ref(3)})let x = ref(4)console.log(obj.a)console.log(obj.b)console.log(obj.c)console.log(x) */// 數(shù)據(jù)let house = ref(4)// 方法function changeToy(){// 必須要Child1組件通過defineExpose將toy屬性暴露出來, 這樣Father組件才能訪問到并修改此toy屬性c1.value.toy = '小豬佩奇'}function changeComputer(){c2.value.computer = '華為'}function getAllChild(refs:{[key:string]:any}){console.log(refs)for (let key in refs){// 這里不需要refs[key].value.book += 3, 是因為refs本身就是個響應式對象, 它會自動解包refs[key].book += 3}}function getAllChild2(){// 使用getCurrentInstance來訪問感覺更加方便console.log(proxy);console.log(proxy.refs); // {c1: Proxy(Object), c2: Proxy(Object)}console.log(proxy.parent); // {uid: 0, vnode: {…}, type: {…}, parent: null, // appContext: {…}, …}console.log(proxy.attrs); // {__vInternal: 1}proxy.refs.c1.book += 2}function getAllChild3(){// console.log($refs); // 注意, 在vue3的setup語法糖中不能直接訪問到$refs// console.log(this.$refs); // 注意, 在vue3的setup語法糖中不能直接訪問到$refsconsole.log(this.proxy); // 這個等價于getCurrentInstance()返回的值console.log(this.proxy == proxy); // trueconsole.log(this.c1); // 這里可以直接訪問到ref='c1'標識的組件this.c1.book += 2}// 向外部提供數(shù)據(jù)defineExpose({house})</script><style scoped>.father {background-color: rgb(165, 164, 164);padding: 20px;border-radius: 10px;}.father button {margin-bottom: 10px;margin-left: 10px;}
</style>
Child1.vue
<template><div class="child1"><h3>子組件1</h3><h4>玩具:{{ toy }}</h4><h4>書籍:{{ book }} 本</h4><button @click="minusHouse($parent)">干掉父親的一套房產(chǎn)</button><button @click="minusHouse2()">干掉父親的一套房產(chǎn)2</button><button @click="minusHouse3()">干掉父親的一套房產(chǎn)3</button></div></template><script setup lang="ts" name="Child1">import { ref,getCurrentInstance } from "vue";const proxy = getCurrentInstance()// 數(shù)據(jù)let toy = ref('奧特曼')let book = ref(3)// 方法function minusHouse(parent:any){// 需要Father組件通過defineExpose將house屬性暴露出來, 這里才可以訪問到parent.house -= 1}function minusHouse2(){// 需要Father組件通過defineExpose將house屬性暴露出來, 這里才可以訪問到console.log(proxy);console.log(proxy.parent);console.log(proxy.parent.exposed);proxy.parent.exposed.house.value -= 1}function minusHouse3(){// 需要Father組件通過defineExpose將house屬性暴露出來, 這里才可以訪問到console.log(this); // Proxy(Object) {proxy: {…}, minusHouse: ?, minusHouse2: ?, …console.log(this.parent); // undefinedconsole.log(this.proxy); // 這個等價于getCurrentInstance()返回的值console.log(this.proxy == proxy); // true}// 把數(shù)據(jù)交給外部defineExpose({toy,book})</script><style scoped>.child1{margin-top: 20px;background-color: skyblue;padding: 20px;border-radius: 10px;box-shadow: 0 0 10px black;}
</style>
Child2.vue
<template><div class="child2"><h3>子組件2</h3><h4>電腦:{{ computer }}</h4><h4>書籍:{{ book }} 本</h4></div>
</template><script setup lang="ts" name="Child2">import { ref } from "vue";// 數(shù)據(jù)let computer = ref('聯(lián)想')let book = ref(6)// 把數(shù)據(jù)交給外部defineExpose({ computer, book })</script><style scoped>.child2 {margin-top: 20px;background-color: orange;padding: 20px;border-radius: 10px;box-shadow: 0 0 10px black;}
</style>
6.7 provide、inject
-
概述:實現(xiàn)祖孫組件直接通信
-
具體使用:
- 在祖先組件中通過
provide
配置向后代組件提供數(shù)據(jù) - 在后代組件中通過
inject
配置來聲明接收數(shù)據(jù)
- 在祖先組件中通過
Father.vue
<template><div class="father"><h3>父組件</h3><h4>銀子:{{ money }}萬元</h4><h4>車子:一輛{{car.brand}}車,價值{{car.price}}萬元</h4><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})function updateMoney(value:number){money.value -= value}// 向后代提供數(shù)據(jù)provide('moneyContext',{money,updateMoney})// (注意數(shù)據(jù)的后面不要.value, 否則不具備響應式)provide('car',car)</script><style scoped>.father {background-color: rgb(165, 164, 164);padding: 20px;border-radius: 10px;}
</style>
Child.vue
<template><div class="child"><h3>我是子組件</h3><GrandChild/></div>
</template><script setup lang="ts" name="Child">import GrandChild from './GrandChild.vue'
</script><style scoped>.child {margin-top: 20px;background-color: skyblue;padding: 20px;border-radius: 10px;box-shadow: 0 0 10px black;}
</style>
GrandChild.vue
<template><div class="grand-child"><h3>我是孫組件</h3><h4>銀子:{{ money }}</h4><h4>車子:一輛{{car.brand}}車,價值{{car.price}}萬元</h4><button @click="updateMoney(6)">花爺爺?shù)腻X</button></div>
</template><script setup lang="ts" name="GrandChild">import { inject } from "vue";let {money,updateMoney} = inject('moneyContext',{money:0,updateMoney:(param:number)=>{}})// 第二個參數(shù)的含義是: 如果沒有提供car, 那么就把第二個參數(shù)作為默認值(這樣可以避免使用car時模板中紅色波浪線)let car = inject('car',{brand:'未知',price:0})</script><style scoped>.grand-child{background-color: orange;padding: 20px;border-radius: 10px;box-shadow: 0 0 10px black;}
</style>
6.8 pinia
直接參考pinia章節(jié)即可。
6.9 slot插槽
1. 默認插槽
Father.vue
<template><div class="father"><h3>父組件</h3><div class="content"><Category title="熱門游戲列表"><ul><li v-for="g in games" :key="g.id">{{ g.name }}</li></ul></Category><Category title="今日美食城市"><img :src="imgUrl" alt=""></Category><Category title="今日影視推薦"><video :src="videoUrl" controls></video></Category></div></div>
</template><script setup lang="ts" name="Father">import Category from './Category.vue'import { ref,reactive } from "vue";let games = reactive([{id:'asgytdfats01',name:'英雄聯(lián)盟'},{id:'asgytdfats02',name:'王者農(nóng)藥'},{id:'asgytdfats03',name:'紅色警戒'},{id:'asgytdfats04',name:'斗羅大陸'}])let imgUrl = ref('https://z1.ax1x.com/2023/11/19/piNxLo4.jpg')let videoUrl = ref('http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4')</script><style scoped>.father {background-color: rgb(165, 164, 164);padding: 20px;border-radius: 10px;}.content {display: flex;justify-content: space-evenly;}img,video {width: 100%;}
</style>
Category.vue
<template><div class="category"><h2>{{title}}</h2><!-- 1. 如果父組件在使用當前組件時, 父組件標簽中沒有傳入內(nèi)容, 那么這里就顯示“默認內(nèi)容” 2. 如果這里這里寫多個slot, 那么父組件標簽中傳入的內(nèi)容就會在每個slot地方都展示一遍3. 其實, 這里省略了name屬性, 它的默認值為default, 即這里相當于: <slot name="default">默認內(nèi)容</slot>--><slot>默認內(nèi)容</slot><!-- 這里同樣會再展示一遍 --><slot name="default">默認內(nèi)容</slot></div></template><script setup lang="ts" name="Category">defineProps(['title'])</script><style scoped>.category {background-color: skyblue;border-radius: 10px;box-shadow: 0 0 10px;padding: 10px;width: 200px;height: 300px;}h2 {background-color: orange;text-align: center;font-size: 20px;font-weight: 800;}
</style>
2. 具名插槽
Father.vue
<template><div class="father"><h3>父組件</h3><div class="content"><Category><!-- v-slot只能用在組件標簽上 或者 <template>標簽中 --><template v-slot:s2><ul><!-- Category標簽中的內(nèi)容可以直接使用Father組件中的數(shù)據(jù) --><li v-for="g in games" :key="g.id">{{ g.name }}</li></ul></template><template v-slot:s1><h2>熱門游戲列表</h2></template></Category><!-- 還可以直接把v-slot直接寫在組件上, 它將會把內(nèi)部的所有內(nèi)容都塞到s2的插槽中 --><Category v-slot:s2><ul><!-- Category標簽中的內(nèi)容可以直接使用Father組件中的數(shù)據(jù) --><li v-for="g in games" :key="g.id">{{ g.name }}</li></ul></Category><Category><template v-slot:s2><img :src="imgUrl" alt=""></template><template v-slot:s1><h2>今日美食城市</h2></template></Category><!-- 簡寫寫法 --><Category><template #s2><!-- Category標簽中的內(nèi)容可以直接使用Father組件中的數(shù)據(jù) --><video video :src="videoUrl" controls></video></template><template #s1><h2>今日影視推薦</h2></template></Category></div></div>
</template><script setup lang="ts" name="Father">import Category from './Category.vue'import { ref,reactive } from "vue";let games = reactive([{id:'asgytdfats01',name:'英雄聯(lián)盟'},{id:'asgytdfats02',name:'王者農(nóng)藥'},{id:'asgytdfats03',name:'紅色警戒'},{id:'asgytdfats04',name:'斗羅大陸'}])let imgUrl = ref('https://z1.ax1x.com/2023/11/19/piNxLo4.jpg')let videoUrl = ref('http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4')</script><style scoped>.father {background-color: rgb(165, 164, 164);padding: 20px;border-radius: 10px;}.content {display: flex;justify-content: space-evenly;}img,video {width: 100%;}h2 {background-color: orange;text-align: center;font-size: 20px;font-weight: 800;}
</style>
Category.vue
<template><div class="category"><slot name="s1">默認內(nèi)容1</slot><slot name="s2">默認內(nèi)容2</slot></div>
</template><script setup lang="ts" name="Category"></script><style scoped>.category {background-color: skyblue;border-radius: 10px;box-shadow: 0 0 10px;padding: 10px;width: 200px;height: 300px;}
</style>
3. 作用域插槽
理解:數(shù)據(jù)在組件的自身,但根據(jù)數(shù)據(jù)生成的結(jié)構(gòu)需要組件的使用者來決定。(新聞數(shù)據(jù)在News
組件中,但使用數(shù)據(jù)所遍歷出來的結(jié)構(gòu)由App
組件決定)
Father.vue
<template><div class="father"><h3>父組件</h3><div class="content"><Game><!-- 這里的params可以拿到所有子組件中傳給<slot>插槽標簽的所有屬性和對應的值 --><!-- 形成的效果就是: 結(jié)構(gòu)是由父組件決定的, 而數(shù)據(jù)的提供者是子組件(至于子組件的這個數(shù)據(jù)哪來的就不用管了, 反正就是有); 或者換句話說: 父組件通過插槽的方式“直接”訪問到了子組件通過插槽傳遞的數(shù)據(jù);--><!-- 這里默認其實是: v-slot:default="params"--><template v-slot="params"><ul><li v-for="y in params.youxi" :key="y.id">{{ y.name }}</li></ul></template></Game><Game><template v-slot="params"><ol><li v-for="item in params.youxi" :key="item.id">{{ item.name }}</li></ol></template></Game><Game><template #default="{youxi}"><h3 v-for="g in youxi" :key="g.id">{{ g.name }}</h3></template></Game></div></div>
</template><script setup lang="ts" name="Father">import Game from './Game.vue'
</script><style scoped>.father {background-color: rgb(165, 164, 164);padding: 20px;border-radius: 10px;}.content {display: flex;justify-content: space-evenly;}img,video {width: 100%;}
</style>
Category.vue
<template><div class="game"><h2>游戲列表</h2><!-- 給插槽提供數(shù)據(jù) --><slot :youxi="games" x="哈哈" y="你好"></slot></div>
</template><script setup lang="ts" name="Game">import {reactive} from 'vue'let games = reactive([{id:'asgytdfats01',name:'英雄聯(lián)盟'},{id:'asgytdfats02',name:'王者農(nóng)藥'},{id:'asgytdfats03',name:'紅色警戒'},{id:'asgytdfats04',name:'斗羅大陸'}])</script><style scoped>.game {width: 200px;height: 300px;background-color: skyblue;border-radius: 10px;box-shadow: 0 0 10px;}h2 {background-color: orange;text-align: center;font-size: 20px;font-weight: 800;}
</style>
7. 其它 API
7.1 shallowRef 與 shallowReactive
shallowRef
-
作用:創(chuàng)建一個響應式數(shù)據(jù),但只對頂層屬性進行響應式處理。
-
用法:
let myVar = shallowRef(initialValue);
-
特點:只跟蹤引用值的變化,不關(guān)心值內(nèi)部的屬性變化。
shallowReactive
-
作用:創(chuàng)建一個淺層響應式對象,只會使對象的最頂層屬性變成響應式的,對象內(nèi)部的嵌套屬性則不會變成響應式的
-
用法:
const myObj = shallowReactive({ ... });
-
特點:對象的頂層屬性是響應式的,但嵌套對象的屬性不是。
總結(jié)
通過使用 shallowRef()
和 shallowReactive()
來繞開深度響應。淺層式 API
創(chuàng)建的狀態(tài)只在其頂層是響應式的,對所有深層的對象不會做任何處理,避免了對每一個內(nèi)部屬性做響應式所帶來的性能成本,這使得屬性的訪問變得更快,可提升性能。
示例
<template><div class="app"><h2>求和為:{{ sum }}</h2><h2>名字為:{{ person.name }}</h2><h2>年齡為:{{ person.age }}</h2><h2>汽車為:{{ car }}</h2><button @click="changeSum">sum+1</button><button @click="changeName">修改名字</button><button @click="changeAge">修改年齡</button><button @click="changePerson">修改整個人</button><span>|</span><button @click="changeBrand">修改品牌</button><button @click="changeColor">修改顏色</button><button @click="changeEngine">修改發(fā)動機</button></div>
</template><script setup lang="ts" name="App">import { ref, reactive, shallowRef, shallowReactive } from 'vue'let sum = shallowRef(0)let person = shallowRef({name: '張三',age: 18})/* 如果使用ref來定義sum和person, 那么下面的方法被調(diào)用時, 數(shù)據(jù)都會發(fā)生改變, 并且都會有響應式;但因為使用shallowRef定義, 因此只有第1層修改才會數(shù)據(jù)發(fā)生改變, 具有響應式, (第1層指的是xxx.value, 不能再點下去了, 否則就不是第1層了)*/function changeSum() {sum.value += 1 // 數(shù)據(jù)發(fā)生改變, 有響應式}function changeName() {person.value.name = '李四' // 數(shù)據(jù)未發(fā)生改變}function changeAge() {person.value.age += 1 // 數(shù)據(jù)未發(fā)生改變}function changePerson() {person.value = { name: 'tony', age: 100 } // 數(shù)據(jù)發(fā)生改變, 有響應式}/* ****************** *//* 如果使用reactive來定義car, 那么下面的方法被調(diào)用時, 數(shù)據(jù)都會發(fā)生改變, 并且都會有響應式;但因為使用shallowReactive定義, 因此只有第1層修改才會數(shù)據(jù)發(fā)生改變, 具有響應式, (第1層指的是brand和options, 不能再點下去了, 否則就不是第1層了)*/let car = shallowReactive({brand: '奔馳',options: {color: '紅色',engine: 'V8'}})function changeBrand() {car.brand = '寶馬'}function changeColor() {car.options.color = '紫色'}function changeEngine() {car.options.engine = 'V12'}</script><style scoped>.app {background-color: #ddd;border-radius: 10px;box-shadow: 0 0 10px;padding: 10px;}button {margin: 0 5px;}
</style>
7.2 readonly 與 shallowReadonly
readonly
-
作用:用于創(chuàng)建一個對象的深只讀副本。
-
用法:
const original = reactive({ ... }); const readOnlyCopy = readonly(original);
-
特點:
- 對象的所有嵌套屬性都將變?yōu)橹蛔x。
- 任何嘗試修改這個對象的操作都會被阻止(在開發(fā)模式下,還會在控制臺中發(fā)出警告)。
-
應用場景:
- 創(chuàng)建不可變的狀態(tài)快照。
- 保護全局狀態(tài)或配置不被修改。
shallowReadonly
-
作用:與
readonly
類似,但只作用于對象的頂層屬性。 -
用法:
const original = reactive({ ... }); const shallowReadOnlyCopy = shallowReadonly(original);
-
特點:
-
只將對象的頂層屬性設置為只讀,對象內(nèi)部的嵌套屬性仍然是可變的。
-
適用于只需保護對象頂層屬性的場景。
-
示例
<template><div class="app"><h2>當前sum1為:{{ sum1 }}</h2><h2>當前sum2為:{{ sum2 }}</h2><button @click="changeSum1">點我sum1+1</button><button @click="changeSum2">點我sum2+1</button><!-- ******************* --><h2>當前car1為:{{ car1 }}</h2><h2>當前car2為:{{ car2 }}</h2><button @click="changeBrand2">修改品牌(car2)</button><button @click="changeColor2">修改顏色(car2)</button><button @click="changePrice2">修改價格(car2)</button></div>
</template><script setup lang="ts" name="App">import { ref, reactive, readonly, shallowReadonly } from "vue";let sum1 = ref(0)// 這里要傳入1個響應式對象, 注意不要.value// 當sum1數(shù)據(jù)發(fā)生變化的時候, sum2也會發(fā)生變化, 但不能直接改sum2, 因為sum2只讀,// (這樣就可以達到一種保護數(shù)據(jù)的目的)let sum2 = readonly(sum1)function changeSum1() {sum1.value += 1}function changeSum2() {sum2.value += 1 // sum2是不能修改的}/******************/let car1 = reactive({brand: '奔馳',options: {color: '紅色',price: 100}})// 這里要傳入1個響應式對象// 當car1數(shù)據(jù)發(fā)生變化的時候, car2也會發(fā)生變化, // 但不能直接改car2的第一層屬性, 因為這里使用的是shallowReadOnly, 意味著car2的第一層屬性都只讀,// 這里也可以使用readOnly, 這就意味著car2的任何屬性都不能改了// (這樣就可以達到一種保護數(shù)據(jù)的目的)let car2 = shallowReadonly(car1)function changeBrand2() {car2.brand = '寶馬'}function changeColor2() {// 由于car2是對car1使用了shallowReadOnly, 因此這里是允許改的car2.options.color = '綠色'}function changePrice2() {car2.options.price += 10}
</script><style scoped>.app {background-color: #ddd;border-radius: 10px;box-shadow: 0 0 10px;padding: 10px;}button {margin: 0 5px;}
</style>
7.3 toRaw 與 markRaw
toRaw
-
作用:用于獲取一個響應式對象的原始對象,
toRaw
返回的對象不再是響應式的,不會觸發(fā)視圖更新。 -
官網(wǎng)描述:這是一個可以用于臨時讀取而不引起代理訪問/跟蹤開銷,或是寫入而不觸發(fā)更改的特殊方法。不建議保存對原始對象的持久引用,請謹慎使用。
-
何時使用? 在需要將響應式對象傳遞給非
Vue
的庫或外部系統(tǒng)時,使用toRaw
可以確保它們收到的是普通對象
markRaw
作用:標記一個對象,使其永遠不會變成響應式的。
例如使用
mockjs
時,為了防止誤把mockjs
變?yōu)轫憫綄ο?#xff0c;可以使用markRaw
去標記mockjs
示例
<template><div class="app"><h2>姓名:{{ person.name }}</h2><h2>年齡:{{ person.age }}</h2><button @click="person.age += 1">修改年齡</button>{{ rawPerson }}<!-- 這里修改rawPerson不會影響到person的數(shù)據(jù)的變化, 并且由于rawPerson不是響應式數(shù)據(jù), 因此上面的{{ rawPerson }}也不會變化 --><button @click="rawPerson.age += 1">修改年齡rawPerson</button><hr><h2>{{ car2 }}</h2><button @click="car2.price += 10">點我價格+10</button></div>
</template><script setup lang="ts" name="App">import { reactive,toRaw,markRaw } from "vue";import mockjs from 'mockjs'/* toRaw */let person = reactive({name:'tony',age:18})// 用于獲取一個響應式對象的原始對象let rawPerson = toRaw(person)console.log('響應式對象',person) // Proxy(Object) {name: 'tony', age: 18}console.log('原始對象',rawPerson) // {name: 'tony', age: 18}console.log('------------------------');/* markRaw */// 如果這里沒加markRaw, 那么這里的這個car就可以作為響應式對象的源頭// 加上了markRaw之后, 就意味著car永遠不能作為響應式對象的源頭, 只能是1個原始的對象, 不能做成1個響應式對象let car = markRaw({brand:'奔馳',price:100})let car2 = reactive(car) // 這里的car2不是響應式的了// 從輸出看, 其實就是加了個標記__v_skip: true, 當遇到這個標記時, 就不對這個對象做響應式處理console.log(car) // {brand: '奔馳', price: 100, __v_skip: true}console.log(car2) // {brand: '奔馳', price: 100, __v_skip: true}// 例如使用mockjs時,為了防止誤把mockjs變?yōu)轫憫綄ο?#xff0c;可以使用 markRaw 去標記mockjslet mockJs = markRaw(mockjs)</script><style scoped>.app {background-color: #ddd;border-radius: 10px;box-shadow: 0 0 10px;padding: 10px;}button {margin:0 5px;}
</style>
7.4 customRef
作用:創(chuàng)建一個自定義的ref
,并對其依賴項跟蹤和更新觸發(fā)進行邏輯控制。
示例
App.vue
<template><div class="app"><h2>{{ msg }}</h2><input type="text" v-model="msg"></div>
</template><script setup lang="ts" name="App">import {ref} from 'vue'import useMsgRef from './useMsgRef'// 使用Vue提供的默認ref定義響應式數(shù)據(jù),數(shù)據(jù)一變,頁面就更新// (這是vue給我們提供的功能, 也是承諾)// let msg = ref('你好')// 使用useMsgRef來定義一個響應式數(shù)據(jù)且有延遲效果let {msg} = useMsgRef('你好',1000)</script><style scoped>.app {background-color: #ddd;border-radius: 10px;box-shadow: 0 0 10px;padding: 10px;}button {margin:0 5px;}
</style>
useMsgRef.ts
import { customRef } from "vue";export default function (initValue: string, delay: number) {// 使用Vue提供的customRef定義響應式數(shù)據(jù)let timer: number// track(跟蹤)、trigger(觸發(fā))let msg = customRef((track, trigger) => {return {// get何時調(diào)用?—— msg被讀取時get() {track() // 告訴Vue數(shù)據(jù)msg很重要,你要對msg進行持續(xù)關(guān)注,一旦msg變化就去更新console.log('get');return initValue},// set何時調(diào)用?—— msg被修改時set(value) {console.log('set');clearTimeout(timer)timer = setTimeout(() => {initValue = valuetrigger() // 通知Vue一下數(shù)據(jù)msg變化了}, delay);}}})return { msg }
}
8. Vue3新組件
8.1 Teleport傳送門
什么是Teleport?—— Teleport 是一種能夠?qū)⑽覀兊?strong>組件html結(jié)構(gòu)移動到指定位置的技術(shù)。
示例
這個示例有個奇怪的地方(css還有這種操作的),給outer加上filter之后,fixed定位就變成相對于父元素定位了,而不是body定位,這時,使用teleport可以解決這個問題,因為它把dom都傳送走了,當然,teleport不僅可以適用于這種情況,也可用于其它場景。
App.vue
<template><div class="outer"><h2>我是App組件</h2><img src="http://www.atguigu.com/images/index_new/logo.png" alt=""><br><!-- 遮罩 --><Modal/></div>
</template><script setup lang="ts" name="App">import Modal from "./Modal.vue";
</script><style>.outer{background-color: #ddd;border-radius: 10px;padding: 5px;box-shadow: 0 0 10px;width: 400px;height: 400px;filter: saturate(200%);}img {width: 270px;}
</style>
Modal.vue
<template><button @click="isShow = true">展示彈窗</button><!-- 數(shù)據(jù)用的還是當前組件的, 但渲染的地方被傳送到了body那里;to這里寫的是選擇器哦;--><teleport to='body'><div class="modal" v-show="isShow"><h2>我是彈窗的標題</h2><p>我是彈窗的內(nèi)容</p><button @click="isShow = false">關(guān)閉彈窗</button></div></teleport></template><script setup lang="ts" name="Modal">import {ref} from 'vue'let isShow = ref(false)</script><style scoped>.modal {width: 200px;height: 150px;background-color: skyblue;border-radius: 10px;padding: 5px;box-shadow: 0 0 5px;text-align: center;position: fixed;left: 50%;top: 20px;margin-left: -100px;}
</style>
8.2 Suspense
- 等待異步組件時渲染一些額外內(nèi)容,讓應用有更好的用戶體驗
- 使用步驟:
- 異步引入組件
- 使用
Suspense
包裹組件,并配置好default
與fallback
示例
App.vue
<template><div class="app"><h2>我是App組件</h2><Child/><Suspense><template v-slot:default><Child/></template><!-- 當組件未加載完成時, 顯示的臨時內(nèi)容 --><template v-slot:fallback><h2>加載中......</h2></template></Suspense></div>
</template><script setup lang="ts" name="App">import {Suspense} from 'vue'import Child from './Child.vue'
</script><style>.app {background-color: #ddd;border-radius: 10px;padding: 10px;box-shadow: 0 0 10px;}
</style>
Child.vue
<template><div class="child"><h2>我是Child組件</h2><h3>當前求和為:{{ sum }}</h3></div>
</template><script setup lang="ts">import {ref} from 'vue'import axios from 'axios'let sum = ref(0);// 當下面多了這行請求數(shù)據(jù)的異步代碼時, Child組件將不會展示出來(setup頂層最外面有async),// 需要父組件在使用時, 借助Suspense組件才能展示Child組件let {data:{content}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')console.log('content',content)/* // 使用這種方式, 可以不借助Suspense組件也能展示Child組件let content = (async function() {let {data:{content}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')return content})(); */</script><style scoped>.child {background-color: skyblue;border-radius: 10px;padding: 10px;box-shadow: 0 0 10px;}
</style>
8.3 全局API轉(zhuǎn)移到應用對象
app.component
app.config
app.directive
app.mount
app.unmount
app.use
示例
import {createApp} from 'vue'
import App from './App.vue'
import Hello from './Hello.vue'// 創(chuàng)建應用
const app = createApp(App)// 全局注冊組件, 然后所有的地方都可以使用Hello這個組件了
app.component('Hello',Hello)// 全局掛載
// 類似于vue2的Vue.prototype.x=99, 然后所有的組件中都可以使用x了
app.config.globalProperties.x = 99// 解決全局掛載x的時候, ts報錯的問題
declare module 'vue' {interface ComponentCustomProperties {x:number}
}// 全局注冊指令, 然后所有的組件中都可以使用v-beauty了, 如: <h1 v-beauty="sum">好開心</h1>
app.directive('beauty',(element,{value})=>{element.innerText += valueelement.style.color = 'green'element.style.backgroundColor = 'yellow'
})// 掛載應用
app.mount('#app')// 卸載應用
setTimeout(() => {app.unmount()
}, 2000);
8.4 其他
-
過渡類名
v-enter
修改為v-enter-from
、過渡類名v-leave
修改為v-leave-from
。 -
keyCode
作為v-on
修飾符的支持。 -
v-model
指令在組件上的使用已經(jīng)被重新設計,替換掉了v-bind.sync。
-
v-if
和v-for
在同一個元素身上使用時的優(yōu)先級發(fā)生了變化。 -
移除了
$on
、$off
和$once
實例方法。 -
移除了過濾器
filter
。 -
移除了
$children
實例propert
。