空間制作網(wǎng)站頭條收錄提交入口
目錄
- 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)建(推薦)
- 2.3.代碼運行
- 3.Vue3核心語法
- 3.1.OptionsAPI(選項式API) 與 CompositionAPI(組合式API)
- 3.2.setup
- 3.3.ref 創(chuàng)建:基本類型的響應(yīng)式數(shù)據(jù)
- 3.4 reactive 創(chuàng)建:對象類型的響應(yīng)式數(shù)據(jù)
- 3.5 ref 創(chuàng)建:對象類型的響應(yīng)式數(shù)據(jù)
- 3.6 ref 對比 reactive
- 3.7.toRefs 與 toRef
- 3.8.computed
- 3.9.watch
- 3.10 watchEffect
- 3.11.標(biāo)簽ref屬性
- 3.12.TS接口、自定義類型、范型
- 3.13.props
- 3.14.生命周期
- 3.15.自定義hook
- 4.路由
- 4.1. 對路由的理解
- 4.2.基本切換效果
- 4.3.兩個注意點
- 4.4.路由器工作模式
- 4.5.命名路由
- 4.6.to的兩種寫法
- 4.7.嵌套路由
- 4.8.路由傳參
- 4.9.路由的 props配置
- 4.10.replace屬性
- 4.11.編程式導(dǎo)航
- 4.12.重定向
- 5.pinia
- 5.1.準(zhǔn)備案例代碼
- 5.2.搭建 pinia 環(huán)境
- 5.3.存儲+讀取數(shù)據(jù)
- 5.4.修改數(shù)據(jù)(三種方式)
- 5.5.storeToRefs
- 5.6.getters
- 5.7.$subscribe
- 5.8.store組合式寫法
- 6.組件通信
- 6.1.props
- 6.2.自定義事件
- 6.3.mitt
- 6.4.v-model
- 6.5.$attrs
- 6.6.$ refs、$parent
- 6.7.provide、inject
- 6.8.slot
- 7.其它API
- 7.1. shallowRef 與 shallowReactive
- 7.2.readonly 與 shallowReadonly
- 7.3.toRaw 與 markRaw
- 7.4.customRef
- 8.Vue3新組件
- 8.1.teleport
- 8.2.Suspense
- 8.3.全局API轉(zhuǎn)移到應(yīng)用對象
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
1.1.性能提升
- 打包體積減少41%
- 初次渲染快55%,更新渲染快133%
- 內(nèi)存減省54%
1.2.源碼升級
- 使用Proxy代替defineProperty實現(xiàn)響應(yīng)式
- 重寫虛擬DOM的實現(xiàn)和Tree-Shaking
1.3.擁抱TypeScript
- Vue3可以更好的支持TypeScript
1.4.新特性
- Composition API (組合API)
- 新內(nèi)置組件
- 其它
2.創(chuàng)建Vue3工程
2.1.基于 vue-cli 創(chuàng)建
目前 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## 選擇Vue 3
## Vue CLI v5.0.8
## ? Please pick a preset: (Use arrow keys)
## > Default ([Vue 3] babel, eslint)
## Default ([Vue 2] babel, eslint)
## Manually select features## 啟動
cd vue_test
npm run serve
2.2. 基于 vite 創(chuàng)建(推薦)
vite 是新一代前端構(gòu)建工具,官網(wǎng)地址:https://vitejs.cn,vite的優(yōu)勢如下:
- 輕量快速的熱得載(HMR),能實現(xiàn)極速的服務(wù)啟動
- 對 TypeScript、JSX、CSS 等支持開箱即用
- 真正的按需編譯,不再等待整個應(yīng)用編譯完成
- webpack構(gòu)建 與 vite 構(gòu)建對比圖如下:
Bundle Based dev server
Native ESM based dev server
- 具體操作如下
## 1.創(chuàng)建命令
npm create vue@latest
## 2.具體配置
## Need to install the following packages:
## create-vue@3.14.2
Ok to proceed? (y) y
## 請輸入項目名稱:
Project name:vue3-project
## 是否使用 TypeScript 語法?
Add TypeScript? Yes
## 是否啟用 JSX 支持
Add JSX Support? No
## 是否引入 Vue Router 進行單頁面應(yīng)用開發(fā)?
Add Vue Router for single Page Application development? No
## 是否引入 Pinia 用于狀態(tài)管理?
Add Pinia for state management? No
## 是否引入 Vitest 用于單元測試?
Add Vitest for Unit Testing? No
## 是否要引入一款端到端(End to End)測試工具?
Add an End-to-End Testing Solution? No
## 是否引入 ESLint 用于代碼質(zhì)量檢測?
Add ESLint for code quality? Yes
## 是否引入 Prettier 用于代碼格式化?
Add Prettier for code formatting? No
- 文件作用
- index.html
- 入口文件,引入 /src/main.ts
- package.json:
- 項目的元數(shù)據(jù)文件,包括項目名稱、版本、描述、作者、依賴項等。
- 定義了項目的腳本,如啟動、構(gòu)建、測試等命令。
- public/:
- 存放靜態(tài)資源,如HTML模板(index.html)、圖片、圖標(biāo)等。
- 這些文件在構(gòu)建時會被復(fù)制到輸出目錄(通常是dist/),并且可能通相對咱徑在項目中引用。
- src/:
- 項目源代碼目錄。
- main.js/main.ts:項目入口文件,用于創(chuàng)建Vue實例并掛載到DOM上。
- App.vue:主組件文件,作為所有頁面組件的容器。
- comments/:存放Vue組件的文件夾,這些組件可以在整個項目中復(fù)用。
- assets/:存放項目中會使用的靜態(tài)資源,如圖片、字體、樣式文件等。這些資源在構(gòu)建時會被處理(如壓縮、轉(zhuǎn)換等)。
- router/:如果項目使用Vue Router進行路由管理,則此文件夾包含路由的配置文件(如index.js 或 index.ts),定義了前端路由的映射關(guān)系。
- store/:如果項目使用Vuex進行狀態(tài)管理,則此文件夾包含Vuex的配置文件(如 index.ts或index.ts),用于管理應(yīng)用的所有組件的狀態(tài)。
- views/:在Vue CLI 3+ 的項目中,這個文件夾通常用來豐防御頁面級的組件,即路由對應(yīng)的組件。
- vite.config.ts
- 配置文件,用于修改webpack配置、添加新的loader選項、配置代理等。
- .gitignore:
- Git版本控制忽略文件,指定哪些文件或文件夾不需要納入GIt版本控制
- tsconfig.json
- TypeScript的配置文件,定譯了TypeScript編譯器的選項。
- index.html
2.3.代碼運行
- 項目入口文件 index.html
<!-- index.html相關(guān)代碼 --><!-- 創(chuàng)建id=app 的容器 --><div id="app"></div><!-- 引入 /src/main.ts --><script type="module" src="/src/main.ts"></script>
- 引入 /src/main.ts
// maint.ts 相關(guān)代碼
// 引入 createApp 創(chuàng)建應(yīng)用
import { createApp } from 'vue'
// 引入 App 根組件
import App from './App.vue'
// createApp(App):以App作為參數(shù)生成一個應(yīng)用實例對象
// mount('#app'):掛載到id=app節(jié)點上。
createApp(App).mount('#app')
3.Vue3核心語法
3.1.OptionsAPI(選項式API) 與 CompositionAPI(組合式API)
- Vue2的API設(shè)計是Options(選項)風(fēng)格
- Vue3的API設(shè)計是CompositionAPI(組合)風(fēng)格
OptionsAPI 的弊端
Options類型的API,數(shù)據(jù)、方法、計算屬性等,是分散在:data、methods、computed中的,若想新境或者改一個需求,就需要分別修改:data、methods、computed,不便于維護和復(fù)用。
CompositionAPI 的優(yōu)勢
可以用函數(shù)的方式,更加優(yōu)雅的組織代碼,讓相關(guān)功能的代碼更加有序的組織在一起。
3.2.setup
setup 概述
setup 是 Vue3 中一個新的配置項,值是一個函數(shù),組件中所用到的:數(shù)據(jù)、方法、計算屬性、監(jiān)視等,均配置在setup中。
特點如下:
- setup函數(shù)返回的對象中的內(nèi)容,可直接在模板中使用
- setup中訪問this是undefined
- setup函數(shù)會在beforeCreate之前調(diào)用,它是“領(lǐng)先”所有鉤子執(zhí)行的。
<template><h2>姓名:{{name}}</h2><br/><h2>年齡:{{age}}</h2><br/><button @click="changeName">修改名字</button><br/><button @click="changeAge">修改年齡</button><br/><button @click="showTel">查看電話</button>
</template>
<script lang="ts">export default{name:'Person',setup(){let name = '張三'let age = 18let tel = '13888888888'function changeName(){name = "zhangsan"console.log(name)}function changeAge(){age += 1console.log(age)}function showTel(){alert(tel)}return {name,age,changeAge,changeName,showTel}// setup的返回值也可以是函數(shù)// return function(){return 'hello'}// return ()=>{return 'hello'} //簡寫// return ()=>'hello' //簡寫}}
</script>
<style>
</style>
setup 與 OptionsAPI的關(guān)系
- Vue2 的選項(data、methods
等)中可以訪問到setup中的屬性、方法。但在setup中不能訪問Vue2的選項(data、methods 等)。 - 如果與Vue2沖突,則setup優(yōu)先
setup語法糖
setup 可以獨立出來
<template><h2>姓名:{{name}}</h2><br/><h2>年齡:{{age}}</h2><br/><button @click="changeName">修改名字</button><br/><button @click="changeAge">修改年齡</button><br/><button @click="showTel">查看電話</button>
</template><script lang="ts">export default{name:'Person'}
</script>
<script lang="ts" setup>let name = '張三'let age = 18let tel = '13888888888'function changeName(){name = "zhangsan"console.log(name)}function changeAge(){age += 1console.log(age)}function showTel(){alert(tel)}
</script>
<style>
</style>
擴展:上述代碼,還需要編寫一個不寫setup的script標(biāo)簽,去指定組件名字,比較麻煩,我們可以借助vite中的插件簡化
- 第一步:npm i vite-plugin-vue-setup-extend -D
- 第二步:vite.config.ts
import VueSetupExtend form 'vite-plugin-vue-setup-extend'//增加引入代碼
export default defineConfig({plugins: [VueSetupExtend(),//增加使用代碼]
})
- 第三步:使用 name=“組件名”
//使用方法
<script setup lang="ts" name="Person">
</script>
3.3.ref 創(chuàng)建:基本類型的響應(yīng)式數(shù)據(jù)
- 作用:定義響應(yīng)式變量
- 語法:let xxx = ref(初始值)
- 返回值:RefImpl的實例對象,簡稱ref對象或ref,ref對象的value屬性是響應(yīng)式的。
- 注意點:
- JS中操作數(shù)據(jù)需要:xxx.value,但模板中不需要.value,直接使用即可。
- 對于let name = ref('張三’) 來說,name不是響應(yīng)式的,name.value是響應(yīng)式的
<template><h2>姓名:{{name}}</h2><br/><h2>年齡:{{age}}</h2><br/><button @click="changeName">修改名字</button><br/><button @click="changeAge">修改年齡</button><br/><button @click="showTel">查看電話</button>
</template>
<script lang="ts" setup name="Person1133">import {ref} from 'vue'//name和age是一個RefImpl的實例對象,簡稱ref對象,它們的value屬性是響應(yīng)式的。let name = ref('張三')let age = ref(18)let tel = '13888888888'function changeName(){name.value = "zhangsan"console.log(name)}function changeAge(){age.value += 1console.log(age)}function showTel(){alert(tel)}
</script>
<style>
</style>
3.4 reactive 創(chuàng)建:對象類型的響應(yīng)式數(shù)據(jù)
作用:定義 響應(yīng)式對象
語法:let 響應(yīng)式對象 = reactive(源對象)
返回值:Proxy的實例對象,簡稱:響應(yīng)式對象
注意點:reactive 定義的響應(yīng)式數(shù)據(jù)是“深層次”的,reactive 會自動解包ref數(shù)據(jù)
<template><h2>姓名:{{person.name}}</h2><br/><h2>年齡:{{person.age}}</h2><br/><button @click="changeName">修改名字</button><br/><button @click="changeAge">修改年齡</button><br/><button @click="showTel">查看電話</button>
</template>
<script lang="ts" setup name="Person1133">import {reactive} from 'vue'let person = reactive({name:'張三',age:18,tel:'13888888888'})console.log(person)function changeName(){person.name = "zhangsan"console.log(person.name)}function changeAge(){person.age += 1console.log(person.age)}function showTel(){alert(person.tel)}
</script>
<style>
</style>
3.5 ref 創(chuàng)建:對象類型的響應(yīng)式數(shù)據(jù)
- ref 接收的數(shù)據(jù)可以是:基本類型、對象類型
- 若ref接收的是對象類型,內(nèi)部其實也是調(diào)用了reactive函數(shù)
<template><h2>姓名:{{person.name}}</h2><br/><h2>年齡:{{person.age}}</h2><br/><button @click="changeName">修改名字</button><br/><button @click="changeAge">修改年齡</button><br/><button @click="showTel">查看電話</button>
</template>
<script lang="ts" setup name="Person1133">import {ref} from 'vue'let person = ref({name:'張三',age:18,tel:'13888888888'})console.log(person)function changeName(){person.value.name = "zhangsan"console.log(person.value.name)}function changeAge(){person.value.age += 1console.log(person.value.age)}function showTel(){alert(person.value.tel)}
</script>
<style>
</style>
3.6 ref 對比 reactive
宏觀角度看:
- ref用來定義:基本數(shù)據(jù)類型、對象類型數(shù)據(jù);
- reactive用來定義:對象類型數(shù)據(jù)。
區(qū)別: - ref創(chuàng)建的變量必須使用.value(可以使用vscode 中的volar插件自動添加.value)。
- reactive 重新分配一個新對象,會失去響應(yīng)式(可以使用Object.assign 去整體替換)。
使用原則: - 若需要一個基本類型的響應(yīng)式數(shù)據(jù),必須使用ref。
- 若需要一個響應(yīng)式對象,層級不深,ref、reactive 都可以。
- 若需要一個響應(yīng)式對象,且層級較深,推薦使用 reactive。
<template><h2>姓名:{{personRef.name}}</h2><br/><h2>年齡:{{personRef.age}}</h2><br/><button @click="changePersonRef">ref修改</button><br/><h2>姓名:{{personReactive.name}}</h2><br/><h2>年齡:{{personReactive.age}}</h2><br/><button @click="changePersonReactive">reactive修改</button><br/>
</template>
<script lang="ts" setup name="Person1133">import {ref,reactive} from 'vue'let personRef = ref({name:'張三',age:18,tel:'13888888888'})let personReactive = reactive({name:'李四',age:19,tel:'13888888888'})function changePersonRef(){// personRef = ref({name:'張三ref',age:28,tel:'13888888888'}) //不行,不是響應(yīng)式personRef.value = {name:'張三ref',age:28,tel:'13888888888'}}function changePersonReactive(){// personReactive = reactive({name:'張三ref',age:28,tel:'13888888888'}) //不行,不是響應(yīng)式Object.assign(personReactive,{name:'李四reactive',age:29,tel:'13888888888'})}
</script>
<style>
</style>
3.7.toRefs 與 toRef
- 作用:將一個響應(yīng)式對象中的每一個屬性,轉(zhuǎn)換為ref對象
- 備注:toRefs 與 toRef 功能一致,但 toRefs 可以批量替換
<template><h2>姓名:{{name}}</h2><br/><h2>年齡:{{person.age}}</h2><br/><button @click="changeName">修改名字</button><br/><button @click="changeAge">修改年齡</button><br/><button @click="showTel">查看電話</button>
</template>
<script lang="ts" setup name="Person1133">import {reactive,toRefs,toRef} from 'vue'let person = reactive({name:'張三',age:18,tel:'13888888888'})let {name,age} = toRefs(person)let nl = toRef(person,'age')console.log(person)function changeName(){name.value = "zhangsan"console.log(name)}function changeAge(){age.value += 1console.log(age)}function showTel(){alert(person.tel)}
</script>
<style>
</style>
3.8.computed
<template><!-- :value(v-bind:value)是單向綁定(數(shù)據(jù)流向頁面),v-model(v-model:value)是雙向綁定 --><!-- 姓:<input type="text" :value="firstName"> -->姓:<input type="text" v-model="firstName"><br>名:<input type="text" v-model="lastName"><br>全名:<span>{{fullName}}</span><button @click="changeFullName">修改成li-si</button>
</template>
<script lang="ts" setup name="Person">import {ref,computed} from 'vue'let firstName = ref('zhang')let lastName = ref('san')//定義的fullName是一個計算屬性,且是只讀的//let fullName = computed(()=>{// return firstName.value.slice(0,1).toUpperCase() + firstName.value.slice(1) + '-' + lastName.value//})//定義的fullName是一個計算屬性,可讀可寫let fullName = computed({get(){return firstName.value.slice(0,1).toUpperCase() + firstName.value.slice(1) + '-' + lastName.value},set(val){const [str1,str2] = val.split('-')firstName.value = str1lastName.value = str2}})function changeFullName(){fullName.value = 'li-si'}
</script>
<style>
</style>
3.9.watch
- 作用:監(jiān)視數(shù)據(jù)變化
- 特點:只能監(jiān)視以下四種數(shù)據(jù):
- ref 定義的數(shù)據(jù)。
- reactive 定義的數(shù)據(jù)。
- 函數(shù)返回一個值(getter 函數(shù))。
- 一個包含上數(shù)內(nèi)容的數(shù)組。
情況一
監(jiān)視 ref 定義的【基本類型】數(shù)據(jù):直接寫數(shù)據(jù)名即可,監(jiān)視的是其value值的改這。
<template><div><h1>sun值:{{ sum }}</h1><button @click="changeSum">sum++</button></div>
</template>
<script lang="ts" setup name="HelloWorld">
import { ref, watch } from 'vue';
//數(shù)據(jù)
const sum = ref(1)
//方法
function changeSum(){sum.value++
}
//監(jiān)視
const stopWathc = watch(sum,(newSum,oldSum)=>{console.log(newSum,oldSum)if(newSum > 10){stopWathc()}
})
console.log(stopWathc)
</script>
<style>
</style>
情況二
監(jiān)視ref定義的【對象類型】數(shù)據(jù):直接寫數(shù)據(jù)名,監(jiān)視的是對象的【地址值】,若想監(jiān)視對象內(nèi)部的數(shù)據(jù),要手動開啟深度監(jiān)視。
注意:
- 若修改的是ref定義的對象的屬性:newValue 和 oldValue 都是新值,因為它們是同一個對象。
- 若修改整個ref定義的對象,newValue 是新值,oldValue 是舊值,因為不是同一個對象了。
<template><div><h1>姓名:{{ person.name }}</h1><h1>年齡:{{ person.age }}</h1><button @click="changeName">修改姓名</button><br><button @click="changeAge">修改年齡</button><br><button @click="changePerson">修改人</button></div>
</template>
<script lang="ts" setup name="HelloWorld">
import { ref, watch } from 'vue';
const person = ref({name:'張三',age:18
})
function changeName(){person.value.name +="~"
}
function changeAge(){person.value.age ++
}
function changePerson(){person.value = {name:'李四',age:28}
}
/*
參數(shù)1:被監(jiān)視的數(shù)據(jù)
參數(shù)2:監(jiān)視的回調(diào)
參數(shù)3:配置對象(deep(深度監(jiān)視)、immediate(立即執(zhí)行
) 等)
*/
watch(person,(newVal,oldVal)=>{console.log(newVal,oldVal)
},{deep:true})</script>
<style>
</style>
情況三
監(jiān)視 reactive 定義的【對象類型】數(shù)據(jù),默認開啟深度監(jiān)視。
<template><div><h1>姓名:{{ person.name }}</h1><h1>年齡:{{ person.age }}</h1><button @click="changeName">修改姓名</button><br><button @click="changeAge">修改年齡</button><br><button @click="changePerson">修改人</button></div>
</template>
<script lang="ts" setup name="HelloWorld">
import { reactive, watch } from 'vue';
const person = reactive({name:'張三',age:18
})
function changeName(){person.name +="~"
}
function changeAge(){person.age ++
}
function changePerson(){Object.assign(person,{name:'李四',age:28})
}
//新值、舊值一樣
watch(person,(newVal,oldVal)=>{console.log(newVal,oldVal)
})
</script>
<style>
</style>
清況四
監(jiān)視 ref 或 reactive 定義的【對象類型】數(shù)據(jù)中的某個屬性,注意如下:
- 若該屬性值不是【對象類型】,需要寫成函數(shù)形式。
- 若該屬性值是【對象類型】,可以直接寫或?qū)懗珊瓟?shù)形式,建議寫成函數(shù)。
結(jié)論:監(jiān)視對象的屬性,建議寫成函數(shù)形式。若屬性是對象,則監(jiān)視的是地址值 ,如果要監(jiān)視對象內(nèi)部,則需要開啟深度監(jiān)視
<template><div><h1>姓名:{{ person.name }}</h1><h1>年齡:{{ person.age }}</h1><h1>汽車:{{ person.car.c1 }}、{{ person.car.c2 }}</h1><button @click="changeName">修改姓名</button><br><button @click="changeAge">修改年齡</button><br><button @click="changeC1">修改車一</button><br><button @click="changeC2">修改車二</button><br><button @click="changeCar">修改車</button></div>
</template>
<script lang="ts" setup name="HelloWorld">
import { reactive, watch } from 'vue';
const person = reactive({name:'張三',age:18,car:{c1:"車一",c2:"車二"}
})
function changeName(){person.name +="~"
}
function changeAge(){person.age ++
}
function changeC1(){person.car.c1 = "車車一"
}
function changeC2(){person.car.c2 = "車車二"
}
function changeCar(){person.car = {c1:'一',c2:'二'}
}
//監(jiān)視一個屬性:基本類型
watch(()=>person.name,(newVal,oldVal)=>{console.log(newVal,oldVal)
})
//監(jiān)視一個屬性:對象類型
//結(jié)論:監(jiān)視對象的屬性,建議寫成函數(shù)形式。若屬性是對象,則監(jiān)視的是地址值 ,如果要監(jiān)視對象內(nèi)部,則需要開啟深度監(jiān)視
watch(()=>person.car,(newVal,oldVal)=>{console.log(newVal,oldVal)
},{deep:true})
</script>
<style>
</style>
情況五
監(jiān)視上述 多個數(shù)據(jù)
<template><div><h1>姓名:{{ person.name }}</h1><h1>年齡:{{ person.age }}</h1><h1>汽車:{{ person.car.c1 }}、{{ person.car.c2 }}</h1><button @click="changeName">修改姓名</button><br><button @click="changeAge">修改年齡</button><br><button @click="changeC1">修改車一</button><br><button @click="changeC2">修改車二</button><br><button @click="changeCar">修改車</button></div>
</template>
<script lang="ts" setup name="HelloWorld">
import { reactive, watch } from 'vue';
const person = reactive({name:'張三',age:18,car:{c1:"車一",c2:"車二"}
})
function changeName(){person.name +="~"
}
function changeAge(){person.age ++
}
function changeC1(){person.car.c1 = "車車一"
}
function changeC2(){person.car.c2 = "車車二"
}
function changeCar(){person.car = {c1:'一',c2:'二'}
}
watch([()=>person.name,()=>person.car.c1],(newVal,oldVal)=>{console.log(newVal,oldVal)
},{deep:true})
</script>
<style>
</style>
3.10 watchEffect
- 立即運行一個函數(shù),同時響應(yīng)式地追蹤其依賴,并在依賴更改時得新執(zhí)行該函數(shù)。
- watch 對經(jīng) watchEffect
- 都能監(jiān)聽響應(yīng)式數(shù)據(jù)的變化,但是監(jiān)聽數(shù)據(jù)變化的方式不同
- watch:要明確指出監(jiān)視的數(shù)據(jù)
- watchEffect:不用明確指出監(jiān)視的數(shù)據(jù)(函數(shù)中用到哪些屬性,那就監(jiān)視哪些屬性)
<template><div><h1>姓名:{{ person.name }}</h1><h1>年齡:{{ person.age }}</h1><button @click="changeName">修改姓名</button><br><button @click="changeAge">修改年齡</button><br><button @click="changePerson">修改人</button></div>
</template>
<script lang="ts" setup name="HelloWorld">
import { reactive, watchEffect } from 'vue';
const person = reactive({name:'張三',age:18
})
function changeName(){person.name +="~"
}
function changeAge(){person.age ++
}
function changePerson(){Object.assign(person,{name:'李四',age:28})
}
//新值、舊值一樣
watchEffect(()=>{console.log(person.age)if(person.age > 35){console.log("35了")}
})
</script>
<style scoped>
</style>
3.11.標(biāo)簽ref屬性
作用:用于注冊模板引用。
- 用在普通DOM標(biāo)簽上,獲取的是DOM節(jié)點
- 用在組件標(biāo)簽上,獲取的是組件實例對象
用在普通DOM標(biāo)簽上
<template><div><h1>中國</h1><h2 ref="h2">北京</h2><button @click="getH2">獲取h2標(biāo)簽實例</button><br></div>
</template>
<script lang="ts" setup name="HelloWorld">
import { ref,defineExpose } from 'vue';
const h2 = ref()
function getH2(){console.log(h2.value)//<h2 data-v-e17ea971="">北京</h2>//data-v-e17ea971 是因為 style scoped 局部樣式導(dǎo)致的
}
const a = ref(0)
const b = ref(1)
const c = ref(2)
defineExpose({a,b})
</script>
<style scoped>button{color: red;}
</style>
用在組件標(biāo)簽上
<template><HelloWorld ref="hw"/><button @click="getHw">獲取HelloWorld實例</button>
</template>
<script lang="ts" setup name="App">import HelloWorld from './components/HelloWorld.vue'import {ref} from 'vue'const hw = ref()function getHw(){console.log(hw.value)console.log(hw.value.a)}
</script>
<style scoped>
</style>
3.12.TS接口、自定義類型、范型
路徑:src/types/index.ts
//定義一個接口,用于限制pesson對象的具體屬性
export interface PersonInter {id:string,name:string,age:number,phone?:string //?表示可有可無
}
//自定義類型
//方法1
// export type PersonArr = Array<PersonInter>
//方法2
export type PersonArr = PersonInter[]
<template><div></div>
</template>
<script lang="ts" setup name="HelloWorld">
import { type PersonInter,type PersonArr } from '@/types';
const person:PersonInter = {id:"doof01",name:"張三",age:19}
const personArr:PersonArr = [
{id:"doof01",name:"張三",age:19},
{id:"doof01",name:"張三",age:19,phone:"13888888888"}
]
console.log(person)
console.log(personArr)
</script>
<style scoped>button{color: red;}
</style>
3.13.props
/src/App.vue
<template><HelloWorld :pl="personList"/>
</template>
<script lang="ts" setup name="App">import { reactive } from 'vue';
import HelloWorld from './components/HelloWorld.vue'
import type { PersonArr } from './types';// Ts限制// const personList:PersonArr = reactive([const personList = reactive<PersonArr>([{id:"01",name:"張三",age:18},{id:"02",name:"李四",age:19,phone:"13888888888"}])</script>
<style scoped>
</style>
/src/components/HellowWorld.vue
<template><div><ul><li v-for="item in pl" :key="item.id">{{ item.id }} -- {{ item.name }}</li></ul></div>
</template>
<script lang="ts" setup name="HelloWorld">
import { type PersonArr } from '@/types';//只接收pl(接收后頁面可使用,但js不能用)
// defineProps(['pl'])//接收pl+限制類型
// defineProps<{pl:PersonArr}>()//接收pl+限制類型+限制必要性+指定默認值
withDefaults(defineProps<{pl?:PersonArr}>(),{pl:()=>[{id:"03",name:"王五",age:18}]
})//接收后頁面可使用,js也要用
// const res = defineProps(['pl'])
// console.log(res.pl)</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銷毀階段:beforeDestory、destroyed
-
Vue3的生命周期
創(chuàng)建階段:setup掛載階段:onBeforeMount、onMounted更新階段:onBeforeUpdate、onUpdated卸載階段:onBeforeUnmount
<!-- /src/App.vue -->
<template><HelloWorld ref="hw" v-if="showHelloWorld"/><button @click="changeHw">卸載HelloWorld</button>
</template>
<script lang="ts" setup name="App">import { ref } from "vue"import HelloWorld from './components/HelloWorld.vue'const showHelloWorld = ref(true)function changeHw(){showHelloWorld.value = !showHelloWorld.value}
</script>
<style scoped>
</style>
<!-- /src/components/HelloWorld.vue -->
<template><div><h1>{{ sum }}</h1><button @click="changeSum">更新</button></div>
</template>
<script lang="ts" setup name="HelloWorld">
import { ref,onBeforeMount, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted } from 'vue';
onBeforeMount(()=>{console.log("掛載前")
})
onMounted(()=>{console.log("掛載后")
})
onBeforeUpdate(()=>{console.log("更新前")
})
onBeforeUpdate(()=>{console.log("更新后")
})
onBeforeUnmount(()=>{console.log("卸載前")
})
onUnmounted(()=>{console.log("卸載后")
})
const sum = ref(0)
function changeSum(){sum.value++
}
</script>
<style scoped>
</style>
3.15.自定義hook
- 本質(zhì)是一個函數(shù),把setup函數(shù)使用的 Composition API 進行封裝,類似于 vue2.x 中的mixin
- 優(yōu)勢:復(fù)用代碼,讓setup中邏輯更清楚易懂。
/src/components/HelloWorld.vue
<template><div><h1>{{ sum }}</h1><button @click="changeSum">sum++</button><br><img v-for="(item,index) in imgArr" :key="index" :src="item"></img><button @click="getImg">加載一張圖片</button></div>
</template>
<script lang="ts" setup name="HelloWorld">import useSum from '@/hooks/useSum';import useDog from '@/hooks/useDog';const {sum,changeSum} = useSum()const {imgArr,getImg} = useDog()
</script>
<style scoped>
</style>
/src/hooks/useDog.ts
<template><HelloWorld/>
</template>
<script lang="ts" setup name="App">import HelloWorld from './components/HelloWorld.vue'
</script>
<style scoped>
</style>
/src/hooks/useSum.ts
import {ref} from 'vue'
export default function(){const sum = ref(0)function changeSum(){sum.value++}return {sum,changeSum}
}
4.路由
4.1. 對路由的理解
4.2.基本切換效果
Vue3 中要使用 vue-router 的最新版本
/src/router/index.ts
// 創(chuàng)建一個路由器,并暴露出去
// 第一步:引入crateRouter
import {createRouter,createWebHashHistory} from 'vue-router'
// 引入組件
import Home from '@/components/Home.vue'
import News from '@/components/News.vue'
import About from '@/components/About.vue'
// 第二步:創(chuàng)建路由器
const router = createRouter({history:createWebHashHistory(),//路由器工作模式routes:[{path:'/home',component:Home},{path:'/news',component:News},{path:'/about',component:About},]
})
export default router
/src/main.ts
import './assets/main.css'
// 引入createApp用于創(chuàng)建應(yīng)用
import { createApp } from 'vue'
// 引入App根組件
import App from './App.vue'
// 引入路由器
import router from './router'
// 創(chuàng)建一個應(yīng)用
const app = createApp(App)
// 使用路由器
app.use(router)
// 掛載整個應(yīng)用到app容器中
app.mount('#app')
/src/App.vue
<template><div class="app"><h1>vue3路由測試</h1><!-- 導(dǎo)航區(qū) --><div class="navigate"><RouterLink to="/home" active-class="aClass">首頁</RouterLink><RouterLink to="/news" active-class="aClass">新聞</RouterLink><RouterLink to="/about" active-class="aClass">關(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';
</script>
<style scoped>.aClass{color: red;}
</style>
/src/components/Home.vue
<template><div><h2>首頁</h2></div>
</template>
<script lang="ts" name="Home">
</script>
<style scoped></style>
/src/components/News.vue
<template><div><h2>新聞</h2></div>
</template>
<script lang="ts" name="News">
</script>
<style scoped></style>
/src/components/About.vue
<template><div><h2>關(guān)于</h2></div>
</template>
<script lang="ts" name="About">
</script>
<style scoped></style>
4.3.兩個注意點
- 路由組件通常放在pages 或 views 文件夾,一般組件通常放在components 文件夾。
- 通過點擊導(dǎo)航,視覺效果上“消失”了路由組件,默認是被銷毀掉的,需要的時候再去掛載。
4.4.路由器工作模式
- history 模式
優(yōu)點:URL更加美觀,不帶有#,更接近傳的網(wǎng)站URL。
缺點:后期項目上線,需要服務(wù)端配合處理路徑問題,否則刷新會有404錯誤
const router = createRouter({history:createWebHistory(),//history模式/******/
})
- hash 模式
優(yōu)點:兼容性更好,因為不需要服務(wù)器端處理路徑
缺點:URL帶有#不太美觀,且在SEO優(yōu)化方面相對較差
const router = createRouter({history:createWebHashHistory(),//hash模式/*******/
})
4.5.命名路由
作用:可以簡化路由跳轉(zhuǎn)及傳參
給路由規(guī)則命名:
routes:[{name:'home',path:'/home',component:Home},{name:'news',path:'/news',component:News},{name:'about',path:'/about',component:About},
]
4.6.to的兩種寫法
<!-- 1.to的字符串寫法 -->
<router-link active-class="active" to="/home">主頁</router-link>
<!-- 2.to的對象寫法 -->
<router-link active-class="active" :to="{path:'/home'}">主頁</router-link>
<router-link active-class="active" :to="{name:'home '}">主頁</router-link>
4.7.嵌套路由
routes:[{name:'home',path:'/home',component:Home},{name:'news',path:'/news',component:News,children:[ //嵌套路由{path:"detail",component:Detail}]},{name:'about',path:'/about',component:About},
]
4.8.路由傳參
query傳參
- 路由設(shè)置
routes:[{name:'home',path:'/home',component:Home},{name:'news',path:'/news',component:News,children:[ //嵌套路由{path:"detail",component:Detail}]},{name:'about',path:'/about',component:About},
]
- 傳遞參數(shù)
<RouterLink
to="'/news/detail?id=${item.id}&title=${item.title}&detail=${item.detial}'">{{ item.title }}</RouterLink>//name:'detail',//用name也可以跳轉(zhuǎn)
path:'/news/detail',
query:{id:item.id,title:item.title,detail:item.detail}
}">{{ item.title }}</RouterLink>
<RouterLink
:to="{
//name:'detail',//用name也可以跳轉(zhuǎn)
path:'/news/detail',
query:{id:item.id,title:item.title,detail:item.detail}
}">{{ item.title }}</RouterLink>
- 接收參數(shù)
import { toRefs } from 'vue';
import { useRoute } from 'vue-router';
const route = useRoute()
const { query } = toRefs(route)
params傳參
- 路由設(shè)置
routes:[{name:'home',path:'/home',component:Home},{name:'news',path:'/news',component:News,children:[{name:"newsDetail",//params傳參,必須使用namepath:"detail/:id/:title/:detail?",//占用,?表示該參數(shù)可選component:Detail}]},{name:'about',path:'/about',component:About},
]
- 傳遞參數(shù)
<RouterLink :to="`/news/detail/${item.id}/${item.title}/${item.detail}`">{{ item.title }}</RouterLink>
<RouterLink :to="{name:'newsDetail',params:{id:item.id,title:item.title,detail:item.detail}}">{{ item.title }}</RouterLink>
- 接收參數(shù)
import { toRefs } from 'vue';
import { useRoute } from 'vue-router';
const route = useRoute()
const { params } = toRefs(route)
4.9.路由的 props配置
作用:讓路由組件更方便的收到參數(shù)(可以將路由參數(shù)作為props傳給組件)
- 路由設(shè)置
{name:'news',path:'/news',component:News,children:[{name:"newsDetail",path:'detail/:id/:title/:detail',component:Detail,//props的布爾值寫法,把收到的params參數(shù),都作為props傳給組件props:true//props的函數(shù)寫法,把返回的對象中的每一組key-value作為props傳給組件// props(route){// return route.query// }//props的對象寫法,把對象中的每一組key-value作為props傳給組件// props:{id:"01",title:"props標(biāo)題",detail:"props內(nèi)容"}}]},
- 參數(shù)接收
defineProps(['id','title','detail'])
4.10.replace屬性
- 作用:按制路由跳轉(zhuǎn)時操作瀏覽器歷史記錄的模式
- 瀏覽器的歷史記錄有兩種寫入方式:分別為push 和 replace:
- push 是追加歷史記錄(默認值)
- replace 是替換當(dāng)前記錄。
- 開啟 replace 模式:
<RouterLink replace :to="{path:'/news/detail'}">News</RouterLink>
4.11.編程式導(dǎo)航
路由組件的兩個重要的屬性:$route 和 $router 變成了兩個 hooks
import {useRoute,useRouter} from 'vue-router'
const route = useRoute()
const router = useRouter()
console.log(route.query)
console.log(route.params)
console.log(router.push)
console.log(router.replace)
import { useRouter } from 'vue-router';interface detailInter {id:string,title:string,detail:string
}
const router = useRouter();
function toPage(detail:detailInter){router.push({name:"newsDetail",params:{id:detail.id,title:detail.title,detail:detail.detail}})
})
4.12.重定向
routes:[{name:'home',path:'/home',component:Home},{name:'news',path:'/news',component:News,children:[{name:"newsDetail",path:'detail/:id/:title/:detail',component:Detail,props:true}]},{name:'about',path:'/about',component:About},{path:'/',//重定向redirect:'/home'}
]
5.pinia
5.1.準(zhǔn)備案例代碼
src/App.vue
<template><div class="app"><h1>APP</h1><Count/><News/></div>
</template>
<script lang="ts" setup name="App">
import Count from './components/Count.vue';
import News from './components/News.vue';
</script>
<style scoped>.aClass{color: red;}
</style>
src/components/Count.vue
<template><div><h2>求和:{{ 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 lang="ts" name="Count" setup>
import { ref } from 'vue';
//數(shù)據(jù)
const sum = ref(1)//當(dāng)前求和
const n = ref(1)//選擇的數(shù)字
//方法
function add(){sum.value = sum.value + n.value
}
function minus(){sum.value = sum.value - n.value
}
</script>
<style scoped>
</style>
src/components/News.vue
<template><div><button @click="getNews">獲取一條新聞</button><ul><li v-for="news in newsList" :key="news.id">{{ news.title }}</li></ul></div>
</template>
<script lang="ts" name="News" setup>
import axios from 'axios';
import { reactive } from 'vue';
import {nanoid} from 'nanoid'
// 數(shù)據(jù)
const newsList = reactive([{id:'01',title:'新聞1'},{id:'02',title:'新聞2'},{id:'03',title:'新聞3'},
])async function getNews() {//連續(xù)解構(gòu),然后給解構(gòu)出來的content命名為title// const {data:{content:title}} = await axios.get("https://api.uomg.com/api/rand.qinghua?format=json")// const obj = {id:nanoid(),title}const obj = {id:nanoid(),title:nanoid()}newsList.unshift(obj)
}
</script>
<style scoped>
</style>
5.2.搭建 pinia 環(huán)境
安裝:npm install pinia
src/main.ts
// 引入createApp用于創(chuàng)建應(yīng)用
import { createApp } from 'vue'
// 引入App根組件
import App from './App.vue'
// 創(chuàng)建一個應(yīng)用
const app = createApp(App)
// 1.引入pinia
import { createPinia } from 'pinia'
// 2.創(chuàng)建pinia
const pinia = createPinia()
// 3.安裝pinia
app.use(pinia)
// 掛載整個應(yīng)用到app容器中
app.mount('#app')
5.3.存儲+讀取數(shù)據(jù)
- Store 是一個保存:狀態(tài)、業(yè)務(wù)邏輯 的實體,每個組件都可以讀取、寫入它。
- 它有三個概念:state、getter、action,相當(dāng)于組件中的:data、computed、methods
- 具體代碼:
src/store/count.ts
//引入defineStore用于創(chuàng)建store
import {defineStore} from 'pinia'
//定義并暴露一個store
export const useCountStore = defineStore('count',{//狀態(tài):真正存儲數(shù)據(jù)的地方state(){return {sum:6}},//動作actions:{},//計算getters:{}
})
src/store/news.ts
import {defineStore} from 'pinia'
export const useNewsStore = defineStore('news',{// 真正存儲數(shù)據(jù)的地方state(){return {newsList:[{id:'01',title:'新聞1'},{id:'02',title:'新聞2'},{id:'03',title:'新聞3'}, ]}}
})
src/components/Count.vue
//讀取
import { useCountStore } from "@/store/count"
const countStore = useCountStore()
console.log(countStore.sum)
5.4.修改數(shù)據(jù)(三種方式)
- 直接修改
countStore.sum = 9
- 批量修改
countStore.$patch({sum:999,school:'學(xué)校'
})
- 借助 action 修改(action 中可以編寫一些業(yè)務(wù)邏緝)
src/store/count.ts
import {defineStore} from 'pinia'
export const useCountStore = defineStore('count',{actions:{//記法increment(value:number){//操作countStore中的值 this.sum += value}}
})
countStore.increment(3 )
5.5.storeToRefs
- 借助 storeToRefs 將 store 中的數(shù)據(jù)轉(zhuǎn)為 ref 對象,方便在模板中使用。
- 注意:pinia 提供的 storeToRefs 只會將數(shù)據(jù)轉(zhuǎn)換,而 toRefs 會將所有東西轉(zhuǎn)換
// storeTorefs 只會對 store 中數(shù)據(jù),進行ref包裹
const countStore = useCountStore()
const {sum} = storeToRefs(countStore)
5.6.getters
概念:當(dāng)state中的數(shù)據(jù),需要經(jīng)過處理后再使用,可以使用getters配置。
//引入defineStore用于創(chuàng)建store
import {defineStore} from 'pinia'
//定義并暴露一個store
export const useCountStore = defineStore('count',{//狀態(tài):真正存儲數(shù)據(jù)的地方state(){return {sum:6,}},//動作actions:{},//計算getters:{//方式1minSum(state){return state.sum /10},//方式2bigSum:state=>state.sum * 10,//方式3//:number 返回的是number類型 addSum():number{return this.sum+10}}
})
const {sum,minSum,bigSum,addSum} = storeToRefs(countStore)
5.7.$subscribe
通過 store 的 $subscribe() 方法偵聽 state 及其變化
talkStore.$subscribe((mutate,state)=>{console.log('LoveTalk',mutate.state)localStorage.setItem('talk',JSON.stringify(talkList.value))
})
5.8.store組合式寫法
export const useCountStore = defineStore('count',()=>{const sum = ref(6)function add(){sum.value++}return {sum,add}
})
6.組件通信
Vue3 中移出了事件總線,可以使用pubsub代替。
- vuex換成了pinia
- 把 .sync 優(yōu)化到了 v-model 里面了
- 把 $listeners 所有的東西,合并到 $attrs 中
常見搭配形式:
組件關(guān)系 | 傳遞方式 |
---|---|
父傳子 | 1.props;2.v-model;3.$refs;4.默認插槽、具名插槽 |
子傳父 | 1.props;2.自定義事件;3.v-model;4.$parent;5.作用域插槽 |
祖?zhèn)鲗O | 1.$attrs;2.provide、injetc |
兄弟間、任意組件間 | 1.mitt,2.pinia |
6.1.props
概述:props是使用頻率最高的一種通方信方式,常用與:父<—>子
父傳子:屬性值是非函數(shù)
子傳父:屬性值是函數(shù)
父組件:src/pages/props/Father.vue
<template><div class="father"><h3>父組件</h3><h4>汽車:{{ car }}</h4><h4>子給的玩具:{{ toy }}</h4><Child :car="car" :sendToy="getToy"/></div>
</template>
<script setup lang="ts" name="Father">
import { ref } from 'vue';
import Child from '../Child.vue';
//數(shù)據(jù)
const car = ref("吉利")
const toy = ref("")
//方法
function getToy(value:string){toy.value = value
}
</script>
<style scoped>
</style>
子組件:src/pages/props/Child.vue
<!-- eslint-disable vue/multi-word-component-names -->
<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ù)
const toy = ref("小汽車")
//聲明接收props
defineProps(['car','sendToy'])
</script>
<style scoped>
</style>
6.2.自定義事件
父組件:src/customEvent/Father.vue
<template><div class="father"><h3>父組件</h3><h4>汽車:{{ car }}</h4><button @click="changeCar1">點我1</button><br><button @click="changeCar2(1,$event,2)">點我2</button><br><button @click="car = 'BYD'">點我3</button><br><!--也可以直接使用$event--><button @click="car = $event.toString()">點我3</button><br><h4>子給的玩具:{{ toy }}</h4><Child @send-toy="saveToy"/></div>
</template>
<script setup lang="ts" name="Father">
import { ref } from 'vue';
import Child from './Child.vue';
const car = ref('吉利')
const toy = ref('')
//調(diào)用方法不傳參,默認可以接收事件event
function changeCar1(x:Event){console.log(x)
}
//調(diào)用方法可以用 $event 占用
function changeCar2(a:number,event:Event,b:number){console.log(a,event,b)
}function saveToy(value:string){toy.value = value
}
</script>
<style scoped>
</style>
子組件:src/customEvent/Child.vue
<template><div class="child"><h2>子組件</h2><button @click="emit('send-toy',toy)">傳給父親</button></div>
</template>
<script setup lang="ts" name="Child">import {ref} from 'vue'const toy = ref('小汽車')const emit = defineEmits(['send-toy'])
</script>
<style scoped>
</style>
6.3.mitt
mitt簡單方法
//引入mitt
import mitt from "mitt";
//調(diào)用mitt得到emitter,emitter能綁定事件、觸發(fā)事件
const emitter = mitt()
//綁定事件
emitter.on('test1',()=>{console.log(3333)
})
setTimeout(()=>{//觸發(fā)事件emitter.emit('test1')
},2000)
//解綁事件
emitter.off('test1')
//全部解綁
emitter.all.clear()
//暴露emitter
export default emitter
案例
父組件:src/pages/mitt/Father.vue
<template><div class="father"><Child1/><Child2/></div>
</template>
<script setup lang="ts" name="Father">
import Child1 from './Child1.vue';
import Child2 from './Child2.vue';
</script>
<style scoped>
</style>
子組件1:src/pages/mitt/Child1.vue
<template><div class="child1"><h3>子組件1</h3><h4>玩具:{{ toy }}</h4><button @click="emitter.emit('send-toy',toy)">給子組件2傳數(shù)據(jù)</button></div>
</template>
<script setup lang="ts" name="Child1">
import emitter from '@/utils/emitter';
import {ref} from 'vue'
const toy = ref('小汽車1')
</script>
<style scoped>
</style>
子組件2:src/pages/mitt/Child2.vue
<template><div class="child2"><h3>子組件2</h3><h4>child1給的玩具:{{ toy }}</h4></div>
</template>
<script setup lang="ts" name="Child2">
import {ref,onUnmounted} from 'vue'
import emitter from '@/utils/emitter';
const toy = ref('')
//給emitter綁定send-toy事件
emitter.on('send-toy',(value:string)=>{toy.value = value
})
//在組件卸載時解綁send-toy事件,減少內(nèi)存占用
onUnmounted(()=>{emitter.off('send-toy')
})
</script>
<style scoped>
</style>
6.4.v-model
父組件:src/pages/v-model/Father.vue
<template><div class="father"><h3>父組件</h3><!-- v-model用在html標(biāo)簽上:雙向綁定 --><input type="text" v-model="username"><br><!-- 底層原理:(<HTMLInputElement>$event.target) 斷言這是html元素,不會是null,防止TS紅--><input type="text" :value="username" @input="username = (<HTMLInputElement>$event.target).value"><br><!-- v-model用在組件標(biāo)簽上 --><!-- <Diyinput v-model="username"/> --><!-- 底層原理 --><Diyinput:modelValue="username"@update:modelValue="username = $event"/><br><!-- v-model重新命名 --><DiyinputTwov-model:modelAccount="account"v-model:modelPassword="password"/><br></div>
</template>
<script setup lang="ts" name="Father">import {ref} from 'vue'import Diyinput from './Diyinput.vue';import DiyinputTwo from './DiyinputTwo.vue';const username = ref("張三")const account = ref("root")const password = ref("123456")
</script>
<style scoped>
</style>
子組件:src/pages/v-model/Diyinput.vue
<template><input type="text" :value="modelValue" @input="emit('update:modelValue',(<HTMLInputElement>$event.target).value)">
</template>
<script setup lang="ts" name="Diyinput">
defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
<style scoped>input{background-color: red;color: white;}
</style>
子組件:src/pages/v-model/DiyinputTwo.vue
<template><input type="text" :value="modelAccount" @input="emit('update:modelAccount',(<HTMLInputElement>$event.target).value)"><input type="text" :value="modelPassword" @input="emit('update:modelPassword',(<HTMLInputElement>$event.target).value)">
</template>
<script setup lang="ts" name="Diyinput">
defineProps(['modelAccount','modelPassword'])
const emit = defineEmits(['update:modelAccount','update:modelPassword'])
</script>
<style scoped>input{background-color: red;color: white;}
</style>
6.5.$attrs
- 概述: $attrs 用于實現(xiàn)當(dāng)前組件的父組件,向當(dāng)前組件的子組件通信(祖—>孫)
- 說明:$atttrs 是一個對象,包含所有父組件傳入的標(biāo)簽屬性
注意:$attrs 會自動排除props中聲明的屬性(可以認為聲明的props被子組件自己“消費”了)
父組件:src/pages/Father.vue
<template><div class="father"><h3>父組件</h3><h4>b:{{ b }}</h4><!-- v-bind="{x:4,y:5}" 相當(dāng)于 :x="4" :y="5" --><Child :a="a" :b="b" v-bind="{x:4,y:5}" :updateB="updateB"/></div>
</template>
<script setup lang="ts" name="Father">
import Child from './Child.vue'
import {ref} from 'vue'
const a = ref(1)
const b = ref(2)
function updateB(value:number){b.value += value
}
</script>
<style scoped>
div{border: 2px solid red;padding:10px;
}
</style>
子組件:src/pages/Child.vue
<template><div class="child"><h3>子組件</h3><!-- <h4>a:{{ a }}</h4> --><h4>其它:{{ $attrs }}</h4><GrandChild v-bind="$attrs"/></div>
</template>
<script setup lang="ts" name="Child">
import GrandChild from './GrandChild.vue';
defineProps(['a'])
</script>
<style scoped>
div{border: 2px solid red;padding:10px;
}
</style>
孫組件:src/pages/GrandChild.vue
<template><div class="grand-child"><h3>孫組件</h3><h4>a:{{ a }},a被子組件props消費掉了</h4><h4>b:{{ b }}</h4><h4>x:{{ x }}</h4><h4>y:{{ y }}</h4><button @click="updateB(2)">增加b</button></div>
</template>
<script setup lang="ts" name="GrandChild">
defineProps(['a','b','x','y','updateB'])
</script>
<style scoped>
div{border: 2px solid red;padding:10px;
}
</style>
6.6.$ refs、$parent
- 概述
- $refs 用于:父—>子
- $parent 用于:子—父
- 原理
屬性 | 說明 |
---|---|
$refs | 值為對象,包含所有被ref屬性標(biāo)識的DOM元素或組件實例 |
$parent | 值為對象,當(dāng)前組件的父組件實例對象 |
父組件:src/pages/refs-parent/Father.vue |
<template><div class="father"><h3>父組件</h3><h4>資產(chǎn):{{ num }} 萬元</h4><button @click="editToy()">修改Child1_toy</button><br><button @click="editComputer()">修改Child2_computer</button><br><button @click="addBook($refs)">增加所有子組件的book</button><br><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 } from 'vue'
const c1 = ref()
const c2 = ref()
const num = ref(4)
function editToy(){c1.value.toy = "挖掘機"
}
function editComputer(){c2.value.computer = "聯(lián)想"
}
function addBook(refs:{[key:string]:any}){for(const key in refs){refs[key].book +=3}
}
defineExpose({num})
</script>
<style scoped>
div{border: 2px solid red;padding:10px;margin: 10px;
}
</style>
子組件1:src/pages/refs-parent/Child1.vue
<template><div class="child1"><h3>子組件1</h3><h4>玩具:{{ toy }}</h4><h4>書本:{{ book }} 本</h4><button @click="minus($parent)">減少父親num</button></div>
</template>
<script setup lang="ts" name="Child1">
import { ref } from 'vue'
const toy = ref('小汽車')
const book = ref(4)
function minus(parent: { num: number}){parent.num --
}
//把數(shù)據(jù)交給外部
defineExpose({toy,book})
</script>
<style scoped>
div{border: 2px solid red;padding:10px;margin: 10px;
}
</style>
子組件2:src/pages/refs-parents/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'
const computer = ref("小米")
const book = ref(5)
//把數(shù)據(jù)交給外部
defineExpose({computer,book})
</script>
<style scoped>
div{border: 2px solid red;padding:10px;margin: 10px;
}
</style>
6.7.provide、inject
- 概述:實現(xiàn)組孫組件直接通信
- 具體使用:
- 在祖先組件中通過 provide 配置向后代組件提供數(shù)據(jù)
- 在后代組件中通過 inject 配置來聲明接收數(shù)據(jù)
父組件:src/pages/provice-inject/Father.vue
<!-- eslint-disable vue/multi-word-component-names -->
<template><div class="father"><h3>父組件</h3><h4>錢:{{ money }}</h4><h4>車:{{ car }}</h4><Child/></div>
</template>
<script setup lang="ts" name="Father">
import Child from './Child.vue'
import { provide, reactive, ref } from 'vue'
const money = ref(100)
const car = reactive({brand:"BYD",price:20
})
function updateMoney(value:number){money.value -= value
}
//向后代提供數(shù)據(jù)
provide('moneyObject',{money,updateMoney})
provide('car',car)
</script>
<style scoped>
div{border: 2px solid red;padding:10px;margin: 10px;
}
</style>
子組件:src/pages/provide-inject/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>
div{border: 2px solid red;padding:10px;margin: 10px;
}
</style>
孫組件:src/pages/provide-inject/Child.vue
<template><div class="child"><h3>孫組件</h3><h4>父的錢:{{ money }}</h4><h4>父的車:{{ car }}</h4><button @click="updateMoney(6)">花錢</button></div>
</template>
<script setup lang="ts" name="GrandChild">
import { inject } from 'vue';const {money,updateMoney} = inject('moneyObject',{money:0,updateMoney:(x: number)=>{}})
const car = inject('car')
</script>
<style scoped>
div{border: 2px solid red;padding:10px;margin: 10px;
}
</style>
6.8.slot
- 默認插槽
- 具名插槽
- 作用域插槽
父組件:src/pages/slot/Father.vue
<template><div class="father"><h2>父組件</h2><div><!-- 默認插槽 --><Category><h3>默認插槽</h3></Category><!-- 具名插槽 --><Category1><template v-slot:slotOne><h3>具名插槽</h3></template></Category1><Category2><template v-slot="params"><h3>作用域插槽</h3><h4>{{ params.title }}</h4><h4>{{ params.games }}</h4></template></Category2></div></div>
</template>
<script setup lang="ts" name="Father">
import Category from './Category.vue'
import Category2 from './Category2.vue'
import Category1 from './Category1.vue'</script>
<style scoped>
div{border: 2px solid red;padding:10px;margin: 10px;
}
</style>
子組件:src/pages/slot/Category.vue
<template><div class="category"><!-- 默認插槽 --><slot></slot><!-- 默認插槽 實際上是 <slot name="default"></slot> --></div>
</template>
<script setup lang="ts" name="Category">
</script>
<style scoped>
div{border: 2px solid red;padding:10px;margin: 10px;
}
</style>
子組件:src/pages/slot/Categroy1.vue
<template><div class="category1"><!-- 具名插槽 --><slot name="slotOne"></slot></div>
</template>
<script setup lang="ts" name="Category1">
</script>
<style scoped>
div{border: 2px solid red;padding:10px;margin: 10px;
}
</style>
子組件:src/pages/slot/Category2.vue
<!-- eslint-disable vue/multi-word-component-names -->
<template><div class="category2"><!-- 作用域插槽 --><slot :games="games" :title="title"></slot></div>
</template>
<script setup lang="ts" name="Category2">
import {ref,reactive} from 'vue'
const games = reactive(["游戲1","游戲2"
])
const title = ref("游戲標(biāo)題")
</script>
<style scoped>
div{border: 2px solid red;padding:10px;margin: 10px;
}
</style>
7.其它API
7.1. shallowRef 與 shallowReactive
shallow:淺的
shallowRef
- 作用:創(chuàng)建一個響應(yīng)式數(shù)據(jù),但只能頂層屬性進行響應(yīng)式處理。
- 用法
let myVar = shallowRef(initialValue);
- 特點:只跟蹤引用值的變化,不關(guān)心內(nèi)部的屬性變化。
shallowReactive
- 作用:創(chuàng)建一個淺層響應(yīng)式對象,只會使對象的最頂層屬性變成響應(yīng)式的,對象內(nèi)部的嵌套屬則不會變成響應(yīng)式折
- 用法:
const myObj = shallowReactive({....})
- 特點:對象的頂層屬性是響應(yīng)式的,但嵌套對象屬性不是。
總結(jié)
通過使用 shallowRef() 和 shallowReactive() 來繞開深度響應(yīng)。淺層式API創(chuàng)建的狀態(tài)只在其頂層是響應(yīng)式的,對所有深層的對象不會做出任何處理,避免對每一個內(nèi)部屬性做響應(yīng)式所帶的性能成功,這使得屬性的訪問變提更快,可提升性能。
7.2.readonly 與 shallowReadonly
readonly
- 作用:用于創(chuàng)建一個對象的深只讀副本。
- 用法:
const original = reactive({......});
const readOnlyCopy = readonly(original);
- 特點:
- 對象的所有嵌套屬性都將變成只讀。
- 任何嘗試修改這個對象的操作都會被阻止(在開發(fā)模式下,還會在控制臺發(fā)出警告)
- 應(yīng)用場景:
- 創(chuàng)建不可變的狀態(tài)快照。
- 保護全局狀態(tài)或配置不被修改。
shallowReadonly
- 作用:與 readonly 類似,但只作用于對象的頂屬屬性。
- 用法:
const original = reactive({...});
const shallowReadOnlyCopy = shallowReadonly(original)
- 特點:
- 只將對象的頂層屬性設(shè)置為只讀,對象內(nèi)部的嵌套屬性仍然是可變的。
- 適用于只需保護對象頂層屬性的場景。
7.3.toRaw 與 markRaw
raw:未經(jīng)加工的
toRaw
- 作用:用于獲取一個響應(yīng)式對象的原始對象,toRaw 返回的對象不再是響應(yīng)式的,不會觸發(fā)視圖更新。
官網(wǎng)描述:這是一個可以用于臨時讀取而不引起代理訪問/跟蹤開銷,或是寫入而不觸 發(fā)更改的特方法。不建議保存對原始對象的持久引用,請謹(jǐn)慎使用。
使用時機:在需要將響應(yīng)式對象傳遞給非Vuer的庫或外部孫統(tǒng)時,使用toRaw 可以確保它們收到的是普通對角
- 編碼:
import { reactive,toRaw,markRaw,isReactive } from 'vue';/* toRaw */
//響應(yīng)式對象
let person = reactive({name:'tony',age:18})
//原始對象
let rawPerson = toRaw(person)
markRaw
- 作用:標(biāo)記一個對象,使其永遠不會變成響應(yīng)式的。
例如使用mockjs時,為了防止把mockjs變成響應(yīng)式對象,可以使用markRaw 去標(biāo)記 mockjs
- 編碼:
/* markRaw */
let citys = markRaw({{id:'01',name:'上海'},{id:'02',name:'北京'}
})
//根據(jù)原始對象citys去創(chuàng)建響應(yīng)式對象citys2 --創(chuàng)建失敗,因為citys被markRaw 標(biāo)記了
let city2 = reactive(citys)
7.4.customRef
作用:創(chuàng)建一個自定義的ref,并對其依賴項跟蹤和更新觸發(fā)進行邏輯控制。
實現(xiàn)防抖效果(useMsgRef.ts)
import {customRef} from 'vue';
export default function(initValue:string,delay:number){//track:跟蹤;trigger:觸發(fā)let msg = customRef((track,trigger)=>{let timer:numberreturn {get(){track()//告訴vue要對msg持續(xù)關(guān)注,一旦變化就更新return initValue},set(value){clearTimeout(timer)timer = setTimeout(()=>{initValue = valuetrigger()//通知Vue數(shù)據(jù)msg變化了},delay)}}})return {msg}
}
import {useMsgRef} from './useMsgRef'
let { msg } = useMsgRef('hellow',2000)
8.Vue3新組件
8.1.teleport
teleport:傳送
Teleport 是一種能夠?qū)⑽覀兊慕M件html結(jié)構(gòu)移動到指定位置的技術(shù)。
<!-- to表示該內(nèi)容顯示在body標(biāo)簽下,也可以使to="#app" 等 -->
<teleport to="body"><div class="model" v-show="isShow"><h2>這是一個彈窗</h2><p>我是彈窗中的一些內(nèi)容</p><button @click="isShow = false">關(guān)閉彈窗</button></div>
</teleport>
8.2.Suspense
suspense:懸念
fallback:退路;應(yīng)變計劃
- 等待異步組件時渲染一些額外內(nèi)容,讓應(yīng)用有更好的用戶體驗
- 使用步驟:
- 異步引入組件
- 使用 Suspense 包裹組件,并配置好 defalut 與 fallback
//?defineAsyncComponent 是Vue 3中用于定義異步組件的API,主要用于實現(xiàn)懶加載(Lazy Loading),即在實際需要時才加載組件,優(yōu)化頁面的性能和提升用戶體驗,尤其適用于大型應(yīng)用和單頁面應(yīng)用(SPA)?
import { defineAsyncComponent,Suspense } from 'vue'
const Child = defineAsyncComponent(()=>import('./Child.vue'))
<template><div class="app"><h3>App組件</h3><Suspense><template v-slot:default><Child/></template></Suspense><template v-slot:fallback><h3>加載中。。。</h3></template></div>
</template>
8.3.全局API轉(zhuǎn)移到應(yīng)用對象
- app.component
定義全局組件
import {crateApp} from 'vue'
import App from './App.vue'
import Hello form './hello.vue'
const app = createApp(App)
app.component('Hello',Hello)
app.mount('#app')
- app.config
定義全局配置
import {crateApp} from 'vue'
import App from './App.vue'
const app = createApp(App)
//任何地方都可以使用x
app.config.globalProperties.x = 9
//避免全局使用x時,編輯器警告
declare module 'vue' {interface ComponentCustomProperties {x:number}
}
app.mount('#app')
- app.directive
定義全局指令
import {crateApp} from 'vue'
import App from './App.vue'
const app = createApp(App)
app.directive('beauty',(element,{value})=>{element.innerText += valueelement.style.color = 'green'element.style.backgroundColor = 'yellow'
})
app.mount('#app')
<h4 v-beauty="1">hellow</h4>
- app.mount
掛載應(yīng)用
import {crateApp} from 'vue'
import App from './App.vue'
const app = createApp(App)
//掛載應(yīng)用
app.mount('#app')
- app.unmount
卸載應(yīng)用
import {crateApp} from 'vue'
import App from './App.vue'
const app = createApp(App)
//掛載應(yīng)用
app.mount('#app')
setTimeout(()=>{卸載應(yīng)用app.unmount()
},2000)
- app.use
安裝插件
import {crateApp} from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
//使用路器
app.use(router)
app.mount('#app')