vue3的基本使用方法詳細(xì)教程
一、初識vue3
1.vue3簡介
- 2020年9月18日,vue3發(fā)布3.0版本,代號大海賊時(shí)代來臨,One Piece
- 特點(diǎn):
- 無需構(gòu)建步驟,漸進(jìn)式增強(qiáng)靜態(tài)的 HTML
- 在任何頁面中作為 Web Components 嵌入
- 單頁應(yīng)用 (SPA)
- 全棧 / 服務(wù)端渲染 (SSR)
- Jamstack / 靜態(tài)站點(diǎn)生成 (SSG)
- 開發(fā)桌面端、移動端、WebGL,甚至是命令行終端中的界面
2.Vue3帶來了什么
- 打包大小減少40%
- 初次渲染快55%,更新渲染快133%
- 內(nèi)存減少54%
3.分析目錄結(jié)構(gòu)
- main.js中的引入
- 在模板中vue3中是可以沒有根標(biāo)簽了,這也是比較重要的改變
- 應(yīng)用實(shí)例并不只限于一個(gè)。createApp API 允許你在同一個(gè)頁面中創(chuàng)建多個(gè)共存的 Vue 應(yīng)用,而且每個(gè)應(yīng)用都擁有自己的用于配置和全局資源的作用域。
//main.js //引入的不再是Vue構(gòu)造函數(shù)了,引入的是一個(gè)名為createApp的工廠函數(shù) import {createApp} from 'vue import App from './App.vue //創(chuàng)建應(yīng)用實(shí)例對象-app(類似于之前vue2中的vm實(shí)例,但是app比vm更輕) createApp(APP).mount('#app') //卸載就是unmount,卸載就沒了 //createApp(APP).unmount('#app') //之前我們是這么寫的,在vue3里面這一塊就不支持了,會報(bào)錯(cuò)的,引入不到 import vue from 'vue'; new Vue({ render:(h) => h(App) }).$mount('#app') //多個(gè)應(yīng)用實(shí)例 const app1 = createApp({ /* ... */ }) app1.mount('#container-1') const app2 = createApp({ /* ... */ }) app2.mount('#container-2')
安裝vue3的開發(fā)者工具
- 方式一: 打開chrome應(yīng)用商店,搜索vue: 里面有個(gè)Vue.js devtools,且下面有個(gè)角標(biāo)beta那個(gè)就是vue3的開發(fā)者工具
- 方式二: 離線模式下,可以直接將包丟到擴(kuò)展程序
二、 常用Composition API(組合式API)
1. setup函數(shù)
理解:Vue3.0中一個(gè)新的額配置項(xiàng),值為一個(gè)函數(shù)
2.setup是所有Composition API(組合api) “表演的舞臺”
組件中所用到的:數(shù)據(jù)、方法等等,均要配置在setup中
setup函數(shù)的兩種返回值:
- 若返回一個(gè)對象,則對象中的屬性、方法,在模板中均可以直接使用。(重點(diǎn)關(guān)注)
- 若返回一個(gè)渲染函數(shù):則可以自定義渲染內(nèi)容。
注意點(diǎn):
- 盡量不要與Vue2.x配置混用
- Vue2.x配置(data ,methos, computed…)中訪問到setup中的屬性,方法
- 但在setup中不能訪問到Vue2.x配置(data.methos,compued…)
- 如果有重名,setup優(yōu)先
- setup不能是一個(gè)async函數(shù),因?yàn)榉祷刂挡辉偈莚eturn的對象,而是promise,模板看不到return對象中的屬性
- 盡量不要與Vue2.x配置混用
import {h} from 'vue' //向下兼容,可以寫入vue2中的data配置項(xiàng) module default { name: 'App', setup(){ //數(shù)據(jù) let name = '張三', let age = 18, //方法 function sayHello(){ console.log(name) }, //f返回一個(gè)對象(常用) return { name, age, sayHello } //返回一個(gè)函數(shù)(渲染函數(shù)) //return () => {return h('h1','學(xué)習(xí)')} return () => h('h1','學(xué)習(xí)') } }
1.1關(guān)于單文件組件<script setup></script >
- 每個(gè) *.vue 文件最多可以包含一個(gè)
<script setup>。(不包括一般的 <script>)
- 這個(gè)腳本塊將被預(yù)處理為組件的 setup() 函數(shù),這意味著它將為每一個(gè)組件實(shí)例都執(zhí)行。
<script setup>
中的頂層綁定都將自動暴露給模板。 <script setup>
是在單文件組件 (SFC) 中使用組合式 API 的編譯時(shí)語法糖。當(dāng)同時(shí)使用 SFC 與組合式 API 時(shí)該語法是默認(rèn)推薦。相比于普通的<script>
語法,它具有更多優(yōu)勢:- 更少的樣板內(nèi)容,更簡潔的代碼。
- 能夠使用純 TypeScript 聲明 props 和自定義事件。這個(gè)我下面是有說明的
- 更好的運(yùn)行時(shí)性能 (其模板會被編譯成同一作用域內(nèi)的渲染函數(shù),避免了渲染上下文代理對象)。
- 更好的 IDE 類型推導(dǎo)性能 (減少了語言服務(wù)器從代碼中抽取類型的工作)。
(1)基本語法:
/* 里面的代碼會被編譯成組件 setup() 函數(shù)的內(nèi)容。 這意味著與普通的 `<script>` 只在組件被首次引入的時(shí)候執(zhí)行一次不同, `<script setup>` 中的代碼會在每次組件實(shí)例被創(chuàng)建的時(shí)候執(zhí)行。*/ <script setup> console.log('hello script setup') </script>
頂層的綁定會被暴露給模板
當(dāng)使用 <script setup>
的時(shí)候,任何在 <script setup>
聲明的頂層的綁定 (包括變量,函數(shù)聲明,以及 import 導(dǎo)入的內(nèi)容) 都能在模板中直接使用:
<script setup> // 變量 const msg = '王二麻子' // 函數(shù) function log() { console.log(msg) } </script> <template> <button @click="log">{{ msg }}</button> </template>
import 導(dǎo)入的內(nèi)容也會以同樣的方式暴露。這意味著我們可以在模板表達(dá)式中直接使用導(dǎo)入的 action 函數(shù),而不需要通過 methods 選項(xiàng)來暴露它:
<script setup> import { say } from './action' </script> <template> <div>{{ say ('hello') }}</div> </template>
(2)響應(yīng)式:
響應(yīng)式狀態(tài)需要明確使用響應(yīng)式 API 來創(chuàng)建。和 setup() 函數(shù)的返回值一樣,ref 在模板中使用的時(shí)候會自動解包:
<script setup> import { ref } from 'vue' const count = ref(0) </script> <template> <button @click="count++">{{ count }}</button> </template>
(3)使用組件:
<script setup>
范圍里的值也能被直接作為自定義組件的標(biāo)簽名使用:
/** *這里 MyComponent 應(yīng)當(dāng)被理解為像是在引用一個(gè)變量。 *如果你使用過 JSX,此處的心智模型是類似的。 *其 kebab-case 格式的 <my-component> 同樣能在模板中使用——不過, *強(qiáng)烈建議使用 PascalCase 格式以保持一致性。同時(shí)這也有助于區(qū)分原生的自定義元素。 */ <script setup> import MyComponent from './MyComponent.vue' </script> <template> <MyComponent /> </template>
動態(tài)組件
/** *由于組件是通過變量引用而不是基于字符串組件名注冊的, *在 <script setup> 中要使用動態(tài)組件的時(shí)候,應(yīng)該使用*動態(tài)的 :is 來綁定: */ <script setup> import Foo from './Foo.vue' import Bar from './Bar.vue' </script> <template> <component :is="Foo" /> <component :is="someCondition ? Foo : Bar" /> </template>
遞歸組件
- 一個(gè)單文件組件可以通過它的文件名被其自己所引用。例如:名為 FooBar.vue 的組件可以在其模板中用
<FooBar/>
引用它自己。 - 注意這種方式相比于導(dǎo)入的組件優(yōu)先級更低。如果有具名的導(dǎo)入和組件自身推導(dǎo)的名字沖突了,可以為導(dǎo)入的組件添加別名:
import { FooBar as FooBarChild } from './components'
命名空間組件
- 可以使用帶 . 的組件標(biāo)簽,例如
<Foo.Bar>
來引用嵌套在對象屬性中的組件。這在需要從單個(gè)文件中導(dǎo)入多個(gè)組件的時(shí)候非常有用:
<script setup> import * as Form from './form-components' </script> <template> <Form.Input> <Form.Label>label</Form.Label> </Form.Input> </template>
(4)使用自定義指令:
- 全局注冊的自定義指令將正常工作。本地的自定義指令在
<script setup>
中不需要顯式注冊,但他們必須遵循 vNameOfDirective 這樣的命名規(guī)范:
<script setup> const vMyDirective = { beforeMount: (el) => { // 在元素上做些操作 } } </script> <template> <h1 v-my-directive>This is a Heading</h1> </template>
- 如果指令是從別處導(dǎo)入的,可以通過重命名來使其符合命名規(guī)范:
<script setup> import { myDirective as vMyDirective } from './MyDirective.js' </script>
(5)defineProps() 和 defineEmits():
- 為了在聲明 props 和 emits 選項(xiàng)時(shí)獲得完整的類型推導(dǎo)支持,我們可以使用 defineProps 和 defineEmits API,它們將自動地在
<script setup>
中可用:
<script setup> const props = defineProps({ foo: String }) const emit = defineEmits(['change', 'delete']) // setup 代碼 </script>
- defineProps 和 defineEmits 都是只能在
<script setup>
中使用的編譯器宏。他們不需要導(dǎo)入,且會隨著<script setup>
的處理過程一同被編譯掉。 - defineProps 接收與 props 選項(xiàng)相同的值,defineEmits 接收與 emits 選項(xiàng)相同的值。
- defineProps 和 defineEmits 在選項(xiàng)傳入后,會提供恰當(dāng)?shù)念愋屯茖?dǎo)。
- 傳入到 defineProps 和 defineEmits 的選項(xiàng)會從 setup 中提升到模塊的作用域。因此,傳入的選項(xiàng)不能引用在 setup 作用域中聲明的局部變量。這樣做會引起編譯錯(cuò)誤。但是,它可以引用導(dǎo)入的綁定,因?yàn)樗鼈円苍谀K作用域內(nèi)。
(5)defineExpose:
- 使用
<script setup>
的組件是默認(rèn)關(guān)閉的——即通過模板引用或者 $parent 鏈獲取到的組件的公開實(shí)例,不會暴露任何在 <script setup>
中聲明的綁定。
//可以通過 defineExpose 編譯器宏來顯式指定在 <script setup> 組件中要暴露出去的屬性: <script setup> import { ref } from 'vue' const a = 1 const b = ref(2) defineExpose({ a, b }) </script> //當(dāng)父組件通過模板引用的方式獲取到當(dāng)前組件的實(shí)例, //獲取到的實(shí)例會像這樣 { a: number, b: number } (ref 會和在普通實(shí)例中一樣被自動解包)
(6)useSlots() 和 useAttrs():
- 在
<script setup>
使用 slots 和 attrs 的情況應(yīng)該是相對來說較為罕見的,因?yàn)榭梢栽谀0逯兄苯油ㄟ^ $slots 和 $attrs 來訪問它們。在你的確需要使用它們的罕見場景中,可以分別用 useSlots 和 useAttrs 兩個(gè)輔助函數(shù):
<script setup> import { useSlots, useAttrs } from 'vue' const slots = useSlots() const attrs = useAttrs() </script> //useSlots 和 useAttrs 是真實(shí)的運(yùn)行時(shí)函數(shù),它的返回與 setupContext.slots 和 setupContext.attrs 等價(jià)。 //它們同樣也能在普通的組合式 API 中使用。
(7)與普通的 <script>
一起使用:
<script setup>
可以和普通的 <script>
一起使用。普通的 <script>
在有這些需要的情況下或許會被使用到:
- 聲明無法在
<script> // 普通 <script>, 在模塊作用域下執(zhí)行 (僅一次) runSideEffectOnce() // 聲明額外的選項(xiàng) export default { inheritAttrs: false, customOptions: {} } </script> <script setup> // 在 setup() 作用域中執(zhí)行 (對每個(gè)實(shí)例皆如此) </script>
(8)頂層 await:
<script setup>
中可以使用頂層 await。結(jié)果代碼會被編譯成 async setup():
<script setup> const post = await fetch(`/api/post/1`).then((r) => r.json()) </script> // 另外,await 的表達(dá)式會自動編譯成在 await 之后保留當(dāng)前組件實(shí)例上下文的格式。
2.ref 函數(shù)
- 作用:定義一個(gè)響應(yīng)式的數(shù)據(jù)
- 語法: const xxx = ref(initValue)
- 創(chuàng)建一個(gè)包含響應(yīng)式數(shù)據(jù)引用對象(reference對象)
- JS中操作數(shù)據(jù):xxx.value
- 模板中讀取數(shù)據(jù):不需要.value,直接: {{xxx}}
- 備注:
- 接收的數(shù)據(jù)可以是:基本類型、也可以是對象類型
- 基本類型的數(shù)據(jù):響應(yīng)式依然靠的是Object.defineProperty()的get和set完成的
- 對象類型的數(shù)據(jù): 內(nèi)部”求助“了Vue3.0中的一個(gè)新的函數(shù)——reactive函數(shù)
3.reactive 函數(shù)
- 作用:定義一個(gè)對象類型的響應(yīng)式數(shù)據(jù)(基本類型別用他,用ref函數(shù))
- 語法:const 代理對象 = reactive(被代理對象)接收一個(gè)對象(或數(shù)組),返回一個(gè)代理對象(proxy對象)
- reactive定義的響應(yīng)式數(shù)據(jù)是”深層次的“
- 內(nèi)部基于ES6的Proxy實(shí)現(xiàn),通過代理對象操作源對象內(nèi)部數(shù)據(jù)進(jìn)行操作
4.Vue3.0中響應(yīng)式原理
- 先來看一看vue2的響應(yīng)式原理
- 對象類型: 通過Object.defineProperty()對屬性的讀取、修改進(jìn)行攔截(數(shù)據(jù)劫持)
- 數(shù)組類型:通過重寫更新數(shù)組的一系列方法來實(shí)現(xiàn)攔截。(對數(shù)組的變更方法進(jìn)行了包裹)
Object.defineProperty( data, 'count', { get(){}, set(){} }) //模擬實(shí)現(xiàn)一下 let person = { name: '張三', age: 15, } let p = {} Object.defineProperty( p, 'name', { configurable: true, //配置這個(gè)屬性表示可刪除的,否則delete p.name 是刪除不了的 false get(){ //有人讀取name屬性時(shí)調(diào)用 return person.name }, set(value){ //有人修改時(shí)調(diào)用 person.name = value } })
- 存在問題:
1. 新增屬性。刪除屬性。界面不會更新
2. 直接通過下表修改數(shù)組,界面不會自動更新
- vue3的響應(yīng)式
- 實(shí)現(xiàn)原理:
- 通過Proxy(代理):攔截對象中任意屬性的變化,包括:屬性值的讀寫、屬性的添加、屬性的刪除等等。
- 通過Reflect(反射):對被代理對象的屬性進(jìn)行操作
- MDN文檔中描述的Proxy與Reflect:可以參考對應(yīng)的文檔
- 實(shí)現(xiàn)原理:
//模擬vue3中實(shí)現(xiàn)響應(yīng)式 let person = { name: '張三', age: 15, } //我們管p叫做代理數(shù)據(jù),管person叫源數(shù)據(jù) const p = new Proxy(person,{ //target代表的是person這個(gè)源對象,propName代表讀取或者寫入的屬性名 get(target,propName){ console.log('有人讀取了p上面的propName屬性') return target[propName] }, //不僅僅是修改調(diào)用,增加的時(shí)候也會調(diào)用 set(target,propName,value){ console.log(`有人修改了p身上的${propName}屬性,我要去更新界面了`) target[propName] = value }, deleteProperty(target,propName){ console.log(`有人刪除了p身上的${propName}屬性,我要去更新界面了`) return delete target[propName] } }) //映射到person上了,捕捉到修改,那就是響應(yīng)式啊
//vue3底層源碼不是我們上面寫的那么low,實(shí)現(xiàn)原理一樣,但是用了一個(gè)新的方式 window.Reflect  let obj = { a: 1, b:2, } //傳統(tǒng)的只能通過try catch去捕獲異常,如果使用這種那么底層源碼將會有一堆try catch try{ Object.defineProperty( obj, 'c', { get(){ return 3 }, }) Object.defineProperty( obj, 'c', { get(){ return 4 }, }) } catch(error) { console.log(error) } //新的方式: 通過Reflect反射對象去操作,相對來說要舒服一點(diǎn),不會要那么多的try catch const x1 = Reflect.defineProperty( obj, 'c', { get(){ return 3 }, }) const x2 = Reflect.defineProperty( obj, 'c', { get(){ return 3 }, }) //x1,和x2是有返回布爾值的 if(x2){ console.log('某某操作成功了') }else { console.log('某某操作失敗了') }
- 所以vue3最終的響應(yīng)式原理如下:
let person = { name: '張三', age: 15, } //我們管p叫做代理數(shù)據(jù),管person叫源數(shù)據(jù) const p = new Proxy(person,{ //target代表的是person這個(gè)源對象,propName代表讀取或者寫入的屬性名 get(target,propName){ console.log('有人讀取了p上面的propName屬性') return Reflect.get(target, propName) }, //不僅僅是修改調(diào)用,增加的時(shí)候也會調(diào)用 set(target,propName,value){ console.log(`有人修改了p身上的${propName}屬性,我要去更新界面了`) Reflect.set(target, propName, value) }, deleteProperty(target,propName){ console.log(`有人刪除了p身上的${propName}屬性,我要去更新界面了`) return Reflect.deleteProperty(target,propName) } })
5.reactive對比ref
從定義數(shù)據(jù)角度對比:
- ref用來定義: 基本數(shù)據(jù)類型
- reactive用來定義: 對象(或數(shù)組)類型數(shù)據(jù)
- 備注: ref也可以用來定義對象(或數(shù)組)類型數(shù)據(jù),它內(nèi)部會自動通過reactive轉(zhuǎn)為代理對象
從原理角度對比:
- ref通過Object.defineProperty()的get和set來實(shí)現(xiàn)響應(yīng)式(數(shù)據(jù)劫持)
- reactive通過Proxy來實(shí)現(xiàn)響應(yīng)式(數(shù)據(jù)劫持),并通過Reflect操作源對象內(nèi)部的數(shù)據(jù)
從使用角度對比:
- ref定義數(shù)據(jù):操作數(shù)據(jù)需要 .value ,讀取數(shù)據(jù)時(shí)模板中直接讀取不需要 .value
- reactive 定義的數(shù)據(jù): 操作數(shù)據(jù)和讀取數(shù)據(jù)均不需要 .value
5.setup的兩個(gè)注意點(diǎn)
- setup執(zhí)行的時(shí)機(jī)
- 在beforeCreate之前執(zhí)行一次,this是undefined
- setup的參數(shù)
- props:值為對象,包含: 組件外部傳遞過來,且組件內(nèi)部聲明接收了屬性
- context:上下文對象
- attrs: 值為對象,包含:組件外部傳遞過來,但沒有在props配置中聲明的屬性,相當(dāng)于 this.$attrs
- slots:收到插槽的內(nèi)容,相當(dāng)于$slots
- emit: 分發(fā)自定義事件的函數(shù),相當(dāng)于this.$emit
//父組件 <script setup> // This starter template is using Vue 3 <script setup> SFCs // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup import HelloWorld from './components/test3.vue'; const hello = (val) =>{ console.log('傳遞的參數(shù)是:'+ val); } </script> <template> <img alt="Vue logo" src="./assets/logo.png" /> <HelloWorld msg="傳遞吧" @hello="hello"> <template v-slot:cacao> <span>是插槽嗎</span> </template> <template v-slot:qwe> <span>meiyou</span> </template> </HelloWorld> </template>
//子組件 export default { name: 'test3', props: ['msg'], emits:['hello'], //這里setup接收兩個(gè)參數(shù),一個(gè)是props,一個(gè)是上下文context setup(props,context){ /** * props就是父組件傳來的值,但是他是Porxy類型的對象 * >Proxy:{msg:'傳遞吧'} * 可以當(dāng)作我們自定義的reactive定義的數(shù)據(jù) */ /** * context是一個(gè)對象 包含以下內(nèi)容: * 1.emit觸發(fā)自定義事件的 * 2.attrs 相當(dāng)于vue2里面的 $attrs 包含:組件外部傳遞過來,但沒有在props配置中聲明的屬性 * 3.slots 相當(dāng)于vue2里面的 $slots * 3.expose 是一個(gè)回調(diào)函數(shù) */ console.log(context.slots); let person = reactive({ name: '張三', age: 17, }) function changeInfo(){ context.emit('hello', 666) } //返回對象 return { person, changeInfo } //返回渲染函數(shù)(了解) 這個(gè)h是個(gè)函數(shù) //return () => h('name','age') } } </script>
6.計(jì)算屬性與監(jiān)視
(1)computed函數(shù)
- 與vue2.x中的寫法一致
- 需要引入computed
<template> <h1>一個(gè)人的信息</h1> <div> 姓: <input type="text" v-model="person.firstName"> 名:<input type="text" v-model="person.lastName"> <div> <span>簡名:{{person.smallName}}</span> <br> <span>全名:{{person.fullName}}</span> </div> </div> </template> <script> import { computed,reactive } from 'vue' export default { name: 'test4', props: ['msg'], emits:['hello'], setup(){ let person = reactive({ firstName: '張', lastName: '三' }) //簡寫形式 person.smallName = computed(()=>{ return person.firstName + '-' + person.lastName }) //完全形態(tài) person.fullName = computed({ get(){ console.log('調(diào)用get'); return person.firstName + '*' + person.lastName }, set(value){ console.log('調(diào)用set'); const nameArr = value.split('*') person.firstName = nameArr[0] person.firstName = nameArr[1] }, }) return { person, } }, } </script>
(2)watch函數(shù)
- 和computed一樣,需要引入api
- 有兩個(gè)小坑:
1.監(jiān)視r(shí)eactive定義的響應(yīng)式數(shù)據(jù)的時(shí)候:oldValue無法獲取到正確的值,強(qiáng)制開啟了深度監(jiān)視(deep配置無效)
2.監(jiān)視r(shí)eactive定義的響應(yīng)式數(shù)據(jù)中某個(gè)屬性的時(shí)候:deep配置有效
具體請看下面代碼以及注釋
<template> <h1>當(dāng)前求和為: {{sum}}</h1> <button @click="sum++">點(diǎn)我+1</button> <hr> <h1>當(dāng)前信息為: {{msg}}</h1> <button @click="msg+='!' ">修改信息</button> <hr> <h2>姓名: {{person.name}}</h2> <h2>年齡: {{person.age}}</h2> <button @click="person.name += '~' ">修改姓名</button> <button @click="person.age++">增長年齡</button> </template> <script> //使用setup的注意事項(xiàng) import { watch,ref,reactive } from 'vue' export default { name: 'test5', props: ['msg'], emits:['hello'], setup(){ let sum = ref(0) let msg = ref('你好啊') let person = reactive({ name: '張三', age: 18, job:{ salary: '15k' }, }) //由于這里的this是指的是undefined,所以使用箭頭函數(shù) //情況一:監(jiān)視r(shí)ef所定義的一個(gè)響應(yīng)式數(shù)據(jù) // watch(sum, (newValue,oldValue)=>{ // console.log('新的值',newValue); // console.log('舊的值',oldValue); // }) //情況二:監(jiān)視r(shí)ef所定義的多個(gè)響應(yīng)式數(shù)據(jù) watch([sum,msg], (newValue,oldValue)=>{ console.log('新的值',newValue); //['sum的newValue', 'msg的newValue'] console.log('舊的值',oldValue); //['sum的oldValue', 'msg的oldValue'] },{immediate: true,deep:true}) //這里vue3的deep是有點(diǎn)小問題的,可以不用deep,(隱式強(qiáng)制deep) //情況三:監(jiān)視r(shí)eactive定義的所有響應(yīng)式數(shù)據(jù), //1.此處無法獲取正確的oldValue(newValue與oldValue是一致值),且目前無法解決 //2.強(qiáng)制開啟了深度監(jiān)視(deep配置無效) /** * 受到碼友熱心評論解釋: 此處附上碼友的解釋供大家參考: * 1. 當(dāng)你監(jiān)聽一個(gè)響應(yīng)式對象的時(shí)候,這里的newVal和oldVal是一樣的,因?yàn)樗麄兪峭粋€(gè)對象【引用地址一樣】, * 即使里面的屬性值會發(fā)生變化,但主體對象引用地址不變。這不是一個(gè)bug。要想不一樣除非這里把對象都換了 * * 2. 當(dāng)你監(jiān)聽一個(gè)響應(yīng)式對象的時(shí)候,vue3會隱式的創(chuàng)建一個(gè)深層監(jiān)聽,即對象里只要有變化就會被調(diào)用。 * 這也解釋了你說的deep配置無效,這里是強(qiáng)制的。 */ watch(person, (newValue,oldValue)=>{ console.log('新的值',newValue); console.log('舊的值',oldValue); }) //情況四:監(jiān)視r(shí)eactive對象中某一個(gè)屬性的值, //注意: 這里監(jiān)視某一個(gè)屬性的時(shí)候可以監(jiān)聽到oldValue watch(()=>person.name, (newValue,oldValue)=>{ console.log('新的值',newValue); console.log('舊的值',oldValue); }) //情況五:監(jiān)視r(shí)eactive對象中某一些屬性的值 watch([()=>person.name,()=>person.age], (newValue,oldValue)=>{ console.log('新的值',newValue); console.log('舊的值',oldValue); }) //特殊情況: 監(jiān)視r(shí)eactive響應(yīng)式數(shù)據(jù)中深層次的對象,此時(shí)deep的配置奏效了 watch(()=>person.job, (newValue,oldValue)=>{ console.log('新的值',newValue); console.log('舊的值',oldValue); },{deep:true}) //此時(shí)deep有用 return { sum, msg, person, } }, } </script>
(3)watchEffect函數(shù)
- watch的套路是:既要指明監(jiān)視的屬性,也要指明監(jiān)視的回調(diào)
- watchEffect的套路是:不用指明監(jiān)視哪個(gè)屬性,監(jiān)視的回調(diào)中用到哪個(gè)屬性,那就監(jiān)視哪個(gè)屬性
- watchEffect有點(diǎn)像computed:
- 但computed注重的計(jì)算出來的值(回調(diào)函數(shù)的返回值),所以必須要寫返回值
- 而watchEffect更注重的是過程(回調(diào)函數(shù)的函數(shù)體),所以不用寫返回值
<script> //使用setup的注意事項(xiàng) import { ref,reactive,watchEffect } from 'vue' export default { name: 'test5', props: ['msg'], emits:['hello'], setup(){ let sum = ref(0) let msg = ref('你好啊') let person = reactive({ name: '張三', age: 18, job:{ salary: '15k' }, }) //用處: 如果是比較復(fù)雜的業(yè)務(wù),發(fā)票報(bào)銷等,那就不許需要去監(jiān)聽其他依賴,只要發(fā)生變化,立馬重新回調(diào) //注重邏輯過程,你發(fā)生改變了我就重新執(zhí)行回調(diào),不用就不執(zhí)行,只執(zhí)行一次 watchEffect(()=>{ //這里面你用到了誰就監(jiān)視誰,里面就發(fā)生回調(diào) const x1 = sum.value console.log('我調(diào)用了'); }) return { sum, msg, person, } }, } </script>
7.生命周期函數(shù)
<template> <h1>生命周期</h1> <p>當(dāng)前求和為: {{sum}}</p> <button @click="sum++">加一</button> </template> <script> //使用setup的注意事項(xiàng) import { ref,reactive,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted } from 'vue' export default { name: 'test7', setup(){ let sum = ref(0) //通過組合式API的形式去使用生命周期鉤子 /** * beforeCreate 和 created 這兩個(gè)生命周期鉤子就相當(dāng)于 setup 所以,不需要這兩個(gè) * * beforeMount ===> onBeforeMount * mounted ===> onMounted * beforeUpdate ===> onBeforeUpdate * updated ===> onUpdated * beforeUnmount ===> onBeforeUnmount * unmounted ===> onUnmounted */ console.log('---setup---'); onBeforeMount(()=>{ console.log('---onBeforeMount---'); }) onMounted(()=>{ console.log('---onMounted---'); }) onBeforeUpdate(()=>{ console.log('---onBeforeUpdate---'); }) onUpdated(()=>{ console.log('---onUpdated---'); }) onBeforeUnmount(()=>{ console.log('---onBeforeUnmount---'); }) onUnmounted(()=>{ console.log('---onUnmounted---'); }) return { sum } }, //這種是外層的寫法,如果想要使用組合式api的話需要放在setup中 beforeCreate(){ console.log('---beforeCreate---'); }, created(){ console.log('---created---'); }, beforeMount(){ console.log('---beforeMount---'); }, mounted(){ console.log('---mounted---'); }, beforeUpdate(){ console.log('---beforeUpdate---'); }, updated(){ console.log('---updated---'); }, //卸載之前 beforeUnmount(){ console.log('---beforeUnmount---'); }, //卸載之后 unmounted(){ console.log('---unmounted---'); } } </script>
8.自定義hook函數(shù)
- 什么是hook函數(shù): 本質(zhì)是一個(gè)函數(shù),把setup函數(shù)中使用的Composition API進(jìn)行了封裝
- 類似于vue2.x中的 mixin
- 自定義hook的優(yōu)勢: 復(fù)用代碼,讓setup中的邏輯更清楚易懂
- 使用hook實(shí)現(xiàn)鼠標(biāo)打點(diǎn)”:
創(chuàng)建文件夾和usePoint.js文件
//usePoint.js import {reactive,onMounted,onBeforeUnmount } from 'vue' function savePoint(){ //實(shí)現(xiàn)鼠標(biāo)打點(diǎn)的數(shù)據(jù) let point = reactive({ x: null, y: null }) //實(shí)現(xiàn)鼠標(biāo)點(diǎn)的方法 const savePoint = (e)=>{ point.x = e.pageX point.y = e.pageY } //實(shí)現(xiàn)鼠標(biāo)打點(diǎn)的生命周期鉤子 onMounted(()=>{ window.addEventListener('click',savePoint) }) onBeforeUnmount(()=>{ window.removeEventListener('click',savePoint) }) return point } export default savePoint
//組件test.vue <template> <p>當(dāng)前求和為: {{sum}} </p> <button @click="sum++">加一</button> <hr> <h2>當(dāng)前點(diǎn)擊時(shí)候的坐標(biāo): x: {{point.x}} y:{{point.y}}</h2> </template> <script> import { ref } from 'vue' import usePoint from '../hooks/usePoint' export default { name: 'test8', setup(props,context){ let sum = ref(0) let point = usePoint() return { sum, point } } } </script>
9.toRef
- 作用: 創(chuàng)建一個(gè)ref對象,其value值指向另一個(gè)對象中的某個(gè)屬性值
- 語法: const name = toRef(person, ‘name’)
- 應(yīng)用:要將響應(yīng)式對象中的某個(gè)屬性單獨(dú)提供給外部使用
- 擴(kuò)展: toRefs與toRef功能一致,但是可以批量創(chuàng)建多個(gè)ref對象,語法: toRefs(person)
<template> <h2>姓名: {{name2}}</h2> <h2>年齡: {{person.age}}</h2> <button @click="person.name += '~' ">修改姓名</button> <button @click="person.age++">增長年齡</button> </template> <script> //使用setup的注意事項(xiàng) import { reactive, toRef, toRefs } from 'vue' export default { name: 'test9', setup(){ let person = reactive({ name: '張三', age: 18, job:{ salary: '15k' }, }) //toRef const name2 = toRef(person,'name') //第一個(gè)參數(shù)是對象,第二個(gè)參數(shù)是鍵名 console.log('toRef轉(zhuǎn)變的是',name2); //ref定義的對象 //toRefs,批量處理對象的所有屬性 //const x = toRefs(person) //console.log('toRefs轉(zhuǎn)變的是',x); //是一個(gè)對象 return { person, name2, ...toRefs(person) } }, } </script>
三、TypeScript 與組合式 API
1.為組件的 props 標(biāo)注類型
//場景一: 使用<script setup> <script setup lang="ts"> const props = defineProps({ foo: { type: String, required: true }, bar: Number }) props.foo // string props.bar // number | undefined </script> //也可以將 props 的類型移入一個(gè)單獨(dú)的接口中 <script setup lang="ts"> interface Props { foo: string bar?: number } const props = defineProps<Props>() </script> //場景二: 不使用<script setup> import { defineComponent } from 'vue' export default defineComponent({ props: { message: String }, setup(props) { props.message // <-- 類型:string } })
- 注意點(diǎn):為了生成正確的運(yùn)行時(shí)代碼,傳給 defineProps() 的泛型參數(shù)必須是以下之一:
//1.一個(gè)類型字面量: defineProps<{ /*... */ }>() //2.對同一個(gè)文件中的一個(gè)接口或?qū)ο箢愋妥置媪康囊? interface Props {/* ... */} defineProps<Props>() //3.接口或?qū)ο笞置骖愋涂梢园瑥钠渌募?dǎo)入的類型引用,但是,傳遞給 defineProps 的泛型參數(shù)本身不能是一個(gè)導(dǎo)入的類型: import { Props } from './other-file' // 不支持! defineProps<Props>()
- Props 解構(gòu)默認(rèn)值
//當(dāng)使用基于類型的聲明時(shí),失去了對 props 定義默認(rèn)值的能力。通過目前實(shí)驗(yàn)性的響應(yīng)性語法糖來解決: <script setup lang="ts"> interface Props { foo: string bar?: number } // 對 defineProps() 的響應(yīng)性解構(gòu) // 默認(rèn)值會被編譯為等價(jià)的運(yùn)行時(shí)選項(xiàng) const { foo, bar = 100 } = defineProps<Props>() </script>
2.為組件的 emits 標(biāo)注類型
//場景一: 使用<script setup> <script setup lang="ts"> const emit = defineEmits(['change', 'update']) // 基于類型 const emit = defineEmits<{ (e: 'change', id: number): void (e: 'update', value: string): void }>() </script> //場景二: 不使用<script setup> import { defineComponent } from 'vue' export default defineComponent({ emits: ['change'], setup(props, { emit }) { emit('change') // <-- 類型檢查 / 自動補(bǔ)全 } })
3.為 ref() 標(biāo)注類型
import { ref } from 'vue' import type { Ref } from 'vue' //1.ref 會根據(jù)初始化時(shí)的值推導(dǎo)其類型: // 推導(dǎo)出的類型:Ref<number> const year = ref(2020) // => TS Error: Type 'string' is not assignable to type 'number'. year.value = '2020' //2.指定一個(gè)更復(fù)雜的類型,可以通過使用 Ref 這個(gè)類型: const year: Ref<string | number> = ref('2020') year.value = 2020 // 成功! //3.在調(diào)用 ref() 時(shí)傳入一個(gè)泛型參數(shù),來覆蓋默認(rèn)的推導(dǎo)行為: // 得到的類型:Ref<string | number> const year = ref<string | number>('2020') year.value = 2020 // 成功! //4.如果你指定了一個(gè)泛型參數(shù)但沒有給出初始值,那么最后得到的就將是一個(gè)包含 undefined 的聯(lián)合類型: // 推導(dǎo)得到的類型:Ref<number | undefined> const n = ref<number>()
4.為reactive() 標(biāo)注類型
import { reactive } from 'vue' //1.reactive() 也會隱式地從它的參數(shù)中推導(dǎo)類型: // 推導(dǎo)得到的類型:{ title: string } const book = reactive({ title: 'Vue 3 指引' }) //2.要顯式地標(biāo)注一個(gè) reactive 變量的類型,我們可以使用接口: interface Book { title: string year?: number } const book: Book = reactive({ title: 'Vue 3 指引' })
5.為 computed() 標(biāo)注類型
import { ref, computed } from 'vue' //1.computed() 會自動從其計(jì)算函數(shù)的返回值上推導(dǎo)出類型: const count = ref(0) // 推導(dǎo)得到的類型:ComputedRef<number> const double = computed(() => count.value * 2) // => TS Error: Property 'split' does not exist on type 'number' const result = double.value.split('') //2.通過泛型參數(shù)顯式指定類型: const double = computed<number>(() => { // 若返回值不是 number 類型則會報(bào)錯(cuò) })
6.為事件處理函數(shù)標(biāo)注類型
//在處理原生 DOM 事件時(shí),應(yīng)該為我們傳遞給事件處理函數(shù)的參數(shù)正確地標(biāo)注類型 <script setup lang="ts"> function handleChange(event) { // 沒有類型標(biāo)注時(shí) `event` 隱式地標(biāo)注為 `any` 類型, // 這也會在 tsconfig.json 中配置了 "strict": true 或 "noImplicitAny": true 時(shí)報(bào)出一個(gè) TS 錯(cuò)誤。 console.log(event.target.value) } </script> <template> <input type="text" @change="handleChange" /> </template> //因此,建議顯式地為事件處理函數(shù)的參數(shù)標(biāo)注類型,需要顯式地強(qiáng)制轉(zhuǎn)換 event 上的屬性: function handleChange(event: Event) { console.log((event.target as HTMLInputElement).value) }
7.為 provide / inject 標(biāo)注類型
/* provide 和 inject 通常會在不同的組件中運(yùn)行。要正確地為注入的值標(biāo)記類型, Vue 提供了一個(gè) InjectionKey 接口,它是一個(gè)繼承自 Symbol 的泛型類型, 可以用來在提供者和消費(fèi)者之間同步注入值的類型: */ import { provide, inject } from 'vue' import type { InjectionKey } from 'vue' const key = Symbol() as InjectionKey<string> provide(key, 'foo') // 若提供的是非字符串值會導(dǎo)致錯(cuò)誤 const foo = inject(key) // foo 的類型:string | undefined //建議將注入 key 的類型放在一個(gè)單獨(dú)的文件中,這樣它就可以被多個(gè)組件導(dǎo)入。 //當(dāng)使用字符串注入 key 時(shí),注入值的類型是 unknown,需要通過泛型參數(shù)顯式聲明: const foo = inject<string>('foo') // 類型:string | undefined //注意注入的值仍然可以是 undefined,因?yàn)闊o法保證提供者一定會在運(yùn)行時(shí) provide 這個(gè)值。 //當(dāng)提供了一個(gè)默認(rèn)值后,這個(gè) undefined 類型就可以被移除: const foo = inject<string>('foo', 'bar') // 類型:string //如果你確定該值將始終被提供,則還可以強(qiáng)制轉(zhuǎn)換該值: const foo = inject('foo') as string
8.為模板引用標(biāo)注類型
//模板引用需要通過一個(gè)顯式指定的泛型參數(shù)和一個(gè)初始值 null 來創(chuàng)建: <script setup lang="ts"> import { ref, onMounted } from 'vue' const el = ref<HTMLInputElement | null>(null) onMounted(() => { el.value?.focus() }) </script> /** 注意為了嚴(yán)格的類型安全,有必要在訪問 el.value 時(shí)使用可選鏈或類型守衛(wèi)。這是因?yàn)橹钡浇M件被掛載前, 這個(gè) ref 的值都是初始的 null,并且在由于 v-if 的行為將引用的元素卸載時(shí)也可以被設(shè)置為 null。 */ <template> <input ref="el" /> </template>
9.為組件模板引用標(biāo)注類型
//有時(shí),你可能需要為一個(gè)子組件添加一個(gè)模板引用,以便調(diào)用它公開的方法。舉例來說,我們有一個(gè) MyModal 子組件,它有一個(gè)打開模態(tài)框的方法 <!-- MyModal.vue --> <script setup lang="ts"> import { ref } from 'vue' const isContentShown = ref(false) const open = () => (isContentShown.value = true) defineExpose({ open }) </script> //為了獲取 MyModal 的類型,我們首先需要通過 typeof 得到其類型,再使用 TypeScript 內(nèi)置的 InstanceType 工具類型來獲取其實(shí)例類型: <!-- App.vue --> <script setup lang="ts"> import MyModal from './MyModal.vue' const modal = ref<InstanceType<typeof MyModal> | null>(null) const openModal = () => { modal.value?.open() } </script> //注意,如果你想在 TypeScript 文件而不是在 Vue SFC 中使用這種技巧,需要開啟 Volar 的Takeover 模式。
四、Vuex與組合式API
- 組合式API 可以通過調(diào)用 useStore 函數(shù),來在 setup 鉤子函數(shù)中訪問 store。這與在組件中使用選項(xiàng)式 API 訪問 this.$store 是等效的。
import { useStore } from 'vuex' export default { setup () { const store = useStore() } }
1.訪問 state 和 getter
- 為了訪問 state 和 getter,需要?jiǎng)?chuàng)建 computed 引用以保留響應(yīng)性,這與在選項(xiàng)式 API 中創(chuàng)建計(jì)算屬性等效。
import { computed } from 'vue' import { useStore } from 'vuex' export default { setup () { const store = useStore() return { // 在 computed 函數(shù)中訪問 state count: computed(() => store.state.count), // 在 computed 函數(shù)中訪問 getter double: computed(() => store.getters.double) } } }
2.訪問 Mutation 和 Action
- 要使用 mutation 和 action 時(shí),只需要在 setup 鉤子函數(shù)中調(diào)用 commit 和 dispatch 函數(shù)。
import { useStore } from 'vuex' export default { setup () { const store = useStore() return { // 使用 mutation increment: () => store.commit('increment'), // 使用 action asyncIncrement: () => store.dispatch('asyncIncrement') } } }
五、 其他的Composition API
1.shallowReactive與shallowRef
- shallowReactive:只處理對象最外層屬性的響應(yīng)式(淺響應(yīng)式)只考慮第一層數(shù)據(jù)的響應(yīng)式。
- shallowRef:只處理基本數(shù)據(jù)類型的響應(yīng)式,不進(jìn)行對象的響應(yīng)式處理,傳遞基本數(shù)據(jù)類型的話跟ref沒有任何區(qū)別,ref是可以進(jìn)行對象的響應(yīng)式處理的
我們正常的ref創(chuàng)建的數(shù)據(jù),里面的.value是一個(gè)proxy,而shallowRef創(chuàng)建的數(shù)據(jù) .value里面是一個(gè)object數(shù)據(jù)類型,所以不會響應(yīng)式數(shù)據(jù)
- 什么時(shí)候使用?:
- 如果有一個(gè)對象數(shù)據(jù),結(jié)構(gòu)比較深,但變化時(shí)只是外層屬性變化 ===> shallowReactive
- 如果有一個(gè)對象數(shù)據(jù),后續(xù)功能不會修改對象中的屬性,而是生新的對象來替換 ===> shallowRef
2.readonly與shallowReadonly
- readonly:讓一個(gè)響應(yīng)式的數(shù)據(jù)變成只讀的(深只讀)
- shallowReadonly: 讓一個(gè)響應(yīng)式數(shù)據(jù)變成只讀的(淺只讀)
- 應(yīng)用場景:不希望數(shù)據(jù)被修改的時(shí)候
<script> import { reactive,readonly,shallowReadonly } from 'vue' export default { name: 'test9', setup(){ let person = reactive({ name: '張三', job:{ salary: '20k', } }) person = readonly(person) //這個(gè)時(shí)候修改人的信息就不會改變了,所有的都不能改 /** * 頁面不進(jìn)行響應(yīng)式的改變,一般存在兩種情況: * 1.setup里面定義的數(shù)據(jù)改變了,但是vue沒有檢測到,這個(gè)時(shí)候是不會改變的 * 2.setup里面定義的數(shù)據(jù)壓根兒就不讓你改,這個(gè)時(shí)候也沒法響應(yīng)式 */ person = shallowReadonly(person) //只有最外層不能修改是只讀的,但是job還是可以改的 return { person } }, } </script>
3.toRaw與markRaw
- toRaw
- 作用:將一個(gè)由reactive生成的響應(yīng)式對象轉(zhuǎn)換為普通對象
- 使用場景:用于讀取響應(yīng)式對象對應(yīng)的普通對象,對這個(gè)普通對象的所有操作,不會引起頁面更新
- markRaw:
- 作用:標(biāo)記一個(gè)對象,使其永遠(yuǎn)不會再成為響應(yīng)式對象
- 使用場景:
- 1.有些值不應(yīng)被設(shè)置成響應(yīng)式的,例如復(fù)雜的第三方類庫等
- 2.當(dāng)渲染具有不可變數(shù)據(jù)的大列表時(shí)候,跳過響應(yīng)式轉(zhuǎn)換可以提高性能
import {reactive,toRaw,markRaw} from 'vue' setup(){ let person = reactive({ name: '張三', }) function showRawPerson(){ const p = toRaw(person) p.age++ console.log(p) } function addCar(){ let car = {name: '奔馳'} person.car = markRaw(car) //一旦這么做時(shí)候,他就永遠(yuǎn)不能當(dāng)成響應(yīng)式數(shù)據(jù)去做了 } }
4.customRef
- 創(chuàng)建一個(gè)自定義的ref,并對其依賴項(xiàng)跟蹤和更新觸發(fā)進(jìn)行顯示控制
- 實(shí)現(xiàn)防抖效果:
<template> <input type="text" v-model="keyword"> <h3>{{keyword}}</h3> </template> <script> import { customRef, ref } from 'vue' export default { name: 'test10', setup(){ let timer; //自定義一個(gè)ref——名為: myRef function myRef(value){ return customRef((track,trigger)=>{ return { get(){ console.log(`有人讀取我的值了,要把${value}給他`); //兩次輸出: v-model讀取 h3里面的插值語法調(diào)了一次 track() //追蹤一下改變的數(shù)據(jù)(提前跟get商量一下,讓他認(rèn)為是有用的) return value }, set(newValue){ console.log(`有人把myRef這個(gè)容器中數(shù)據(jù)改了:${newValue}`); clearTimeout(timer) timer = setTimeout(()=>{ value = newValue trigger() //通知vue去重新解析模板,重新再一次調(diào)用get() },500) } } }) } // let keyword = ref('hello') //使用內(nèi)置提供的ref let keyword = myRef('hello') //使用自定義的ref return { keyword, } }, } </script>
5.provide與inject
- 作用:實(shí)現(xiàn)祖孫組件間的通信
- 套路:父組件有一個(gè)provide選項(xiàng)提供數(shù)據(jù),子組件有一個(gè)inject選項(xiàng)來開始使用這些數(shù)據(jù)
- 具體寫法:
//父組件 <script setup> import { ref,reactive,toRefs,provide } from 'vue'; import ChildVue from './components/Child.vue'; let car = reactive({ name: '奔馳', price: '40w' }) provide('car',car) //給自己的后代組件傳遞數(shù)據(jù) const {name, price} = toRefs(car) </script> <template> <div> <h3>我是父組件, {{name}}--{{price}}</h3> <ChildVue></ChildVue> </div> </template> <style> .app{ background-color: gray; padding: 10px; box-sizing: border-box; } </style>
//子組件 <script setup> import { ref } from '@vue/reactivity'; import SonVue from './Son.vue'; </script> <template> <div> <h3>我是子組件</h3> <SonVue></SonVue> </div> </template> <style> .app2{ background-color: rgb(82, 150, 214); padding: 10px; box-sizing: border-box; } </style>
//孫組件 <script setup> import { ref,inject } from 'vue'; let car = inject('car') //拿到父組件的數(shù)據(jù) const {name, price} = car </script> <template> <div> <h3>我是孫組件</h3> <p>{{name}}-{{price}}</p> </div> </template> <style> .app3{ background-color: rgb(231, 184, 56); padding: 10px; box-sizing: border-box; } </style>
6.響應(yīng)式數(shù)據(jù)的判斷
- isRef:檢查一個(gè)值是否為ref對象
- isReactivce:檢查一個(gè)對象是否是由reactive創(chuàng)建的響應(yīng)式代理
- isReadonly:檢查一個(gè)對象是否由readonly創(chuàng)建的只讀代理
- isProxy:檢查一個(gè)對象是否由reactive或者readonly方法創(chuàng)建的代理
六、Composition API的優(yōu)勢
1.傳統(tǒng)options API存在的問題
- 使用傳統(tǒng)的Options API中,新增或者修改一個(gè)需求,就需要分別在data,methods,computed里面修改
2.Composition API的優(yōu)勢
- 我們可以更加優(yōu)雅的組織我們的代碼,函數(shù),讓相關(guān)功能的代碼更加有序的組織在一起
七、新的組件
1.Transition
- 會在一個(gè)元素或組件進(jìn)入和離開 DOM 時(shí)應(yīng)用動畫
- 它是一個(gè)內(nèi)置組件,這意味著它在任意別的組件中都可以被使用,無需注冊。它可以將進(jìn)入和離開動畫應(yīng)用到通過默認(rèn)插槽傳遞給它的元素或組件上。進(jìn)入或離開可以由以下的條件之一觸發(fā):
- 由 v-if 所觸發(fā)的切換
- 由 v-show 所觸發(fā)的切換
- 由特殊元素 切換的動態(tài)組件
<button @click="show = !show">切換</button> <Transition> <p v-if="show">HelloWord</p> </Transition> //當(dāng)一個(gè) <Transition> 組件中的元素被插入或移除時(shí),會發(fā)生下面這些事情 /** 1.Vue 會自動檢測目標(biāo)元素是否應(yīng)用了 CSS 過渡或動畫。如果是,則一些 CSS 過渡 class 會在適當(dāng)?shù)臅r(shí)機(jī)被添加和移除 2.如果有作為監(jiān)聽器的 JavaScript 鉤子,這些鉤子函數(shù)會在適當(dāng)時(shí)機(jī)被調(diào)用 3.如果沒有探測到 CSS 過渡或動畫、也沒有提供 JavaScript 鉤子,那么 DOM 的插入、刪除操作將在瀏覽器的下一個(gè)動畫幀后執(zhí)行 */ //針對CSS 的過渡效果 /** 1.v-enter-from:進(jìn)入動畫的起始狀態(tài)。在元素插入之前添加,在元素插入完成后的下一幀移除。 2.v-enter-active:進(jìn)入動畫的生效狀態(tài)。應(yīng)用于整個(gè)進(jìn)入動畫階段。在元素被插入之前添加,在過渡或動畫完成之后移除。這個(gè) class 可以被用來定義進(jìn)入動畫的持續(xù)時(shí)間、延遲與速度曲線類型 3.v-enter-to:進(jìn)入動畫的結(jié)束狀態(tài)。在元素插入完成后的下一幀被添加 (也就是 v-enter-from 被移除的同時(shí)),在過渡或動畫完成之后移除。 4.v-leave-from:離開動畫的起始狀態(tài)。在離開過渡效果被觸發(fā)時(shí)立即添加,在一幀后被移除 5.v-leave-active:離開動畫的生效狀態(tài)。應(yīng)用于整個(gè)離開動畫階段。在離開過渡效果被觸發(fā)時(shí)立即添加,在過渡或動畫完成之后移除。這個(gè) class 可以被用來定義離開動畫的持續(xù)時(shí)間、延遲與速度曲線類型。 6.v-leave-to:離開動畫的結(jié)束狀態(tài)。在一個(gè)離開動畫被觸發(fā)后的下一幀被添加 (也就是 v-leave-from 被移除的同時(shí)),在過渡或動畫完成之后移除。 */
.v-enter-active, .v-leave-active { transition: opacity 0.5s ease; } .v-enter-from, .v-leave-to { opacity: 0; }
2.Fragment
- 在vue2中:組件必須有一個(gè)根標(biāo)簽
- 在vue3中:組件可以沒有根標(biāo)簽,內(nèi)部會將多個(gè)標(biāo)簽包含在一個(gè)Fragment虛擬元素中
- 好處:減少標(biāo)簽層級,減少內(nèi)存占用
3.Teleport
- 什么是Teleport? —— Teleport是一種能夠?qū)⑽覀兘M件html結(jié)構(gòu)移動到指定位置的技術(shù)(開發(fā)的時(shí)候非常有用)
//彈窗實(shí)現(xiàn) <script setup> import { ref,inject } from 'vue'; let isShow = ref(false) </script> <template> <div> <button @click="isShow = true">點(diǎn)我彈窗</button> <teleport to="body"> //定位到body <div v-if="isShow"> <div> <h4>我是一個(gè)彈窗</h4> <h5>內(nèi)容</h5> <h5>內(nèi)容</h5> <h5>內(nèi)容</h5> <button @click="isShow = false">關(guān)閉</button> </div> </div> </teleport> </div> </template> <style> .dialog{ width: 300px; height: 300px; text-align: center; position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); background-color: blueviolet; margin: 0 auto; } .mask{ position: absolute; top: 0; left: 0; bottom: 0; right: 0; background-color: rgba(0, 0, 0, 0.5); } </style>
4.Suspense
<script setup> import { defineAsyncComponent } from 'vue'; //引入異步組件 const ChildVue = defineAsyncComponent(()=> import('./components/Child.vue')) //這叫做動態(tài)引入 //這種引入叫做異步引入,如果app不出來的話,那么Child組件也不會引入進(jìn)來,有一個(gè)先后順序 // import ChildVue from './components/Child.vue'; //靜態(tài)引入 // 得等,等所有的組件加載完成之后app才會一起出現(xiàn) /** * Suspense這個(gè)標(biāo)簽,底層就內(nèi)置了插槽,就可以解決異步引入有時(shí)候刷新先后出來慢的問題 * v-slot:default 表示默認(rèn)的輸出組件 * v-slot:fallback 表示如果頁面加載的慢了,會優(yōu)先展示這個(gè)內(nèi)容,有點(diǎn)像刷新頁面的時(shí)候數(shù)據(jù)回來的慢了,就加載一會兒 */ </script> <template> <div> <h3>我是父組件</h3> <Suspense> <template v-slot:default> <ChildVue></ChildVue> </template> <template v-slot:fallback> <h3>稍等,加載中....</h3> </template> </Suspense> </div> </template> <style> .app{ background-color: gray; padding: 10px; box-sizing: border-box; } </style> /**還有一種方法就是在子組件中,setup返回一個(gè)promise對象,這里之所以可以使用setup返回promise的原因 是: 我們引入的是異步組件且使用了<Suspense></Suspense> */
- 等待異步組件時(shí)渲染一些后備內(nèi)容,獲得更好的用戶體驗(yàn)
八: 新的生命周期鉤子
1.常見的生命周期鉤子
onMounted() onUpdated() onUnmounted() onBeforeMount() onBeforeUpdate() onBeforeUnmount() onActivated() onDeactivated() onServerPrefetch()
2.新的生命周期鉤子
//1.onErrorCaptured():注冊一個(gè)鉤子,在捕獲了后代組件傳遞的錯(cuò)誤時(shí)調(diào)用。 function onErrorCaptured(callback: ErrorCapturedHook): void type ErrorCapturedHook = ( err: unknown, instance: ComponentPublicInstance | null, info: string ) => boolean | void //2.onRenderTracked():注冊一個(gè)調(diào)試鉤子,當(dāng)組件渲染過程中追蹤到響應(yīng)式依賴時(shí)調(diào)用。 function onRenderTracked(callback: DebuggerHook): void type DebuggerHook = (e: DebuggerEvent) => void type DebuggerEvent = { effect: ReactiveEffect target: object type: TrackOpTypes /* 'get' | 'has' | 'iterate' */ key: any } //3.onRenderTriggered():注冊一個(gè)調(diào)試鉤子,當(dāng)響應(yīng)式依賴的變更觸發(fā)了組件渲染時(shí)調(diào)用。 function onRenderTriggered(callback: DebuggerHook): void type DebuggerHook = (e: DebuggerEvent) => void type DebuggerEvent = { effect: ReactiveEffect target: object type: TriggerOpTypes /* 'set' | 'add' | 'delete' | 'clear' */ key: any newValue?: any oldValue?: any oldTarget?: Map<any, any> | Set<any> } //4.onServerPrefetch():注冊一個(gè)異步函數(shù),在組件實(shí)例在服務(wù)器上被渲染之前調(diào)用。 function onServerPrefetch(callback: () => Promise<any>): void /** 補(bǔ)充:1.如果這個(gè)鉤子返回了一個(gè) Promise,服務(wù)端渲染會在渲染該組件前等待該 Promise 完成。 2.這個(gè)鉤子僅會在服務(wù)端渲染中執(zhí)行,可以用于執(zhí)行一些僅存在于服務(wù)端的數(shù)據(jù)抓取過程 */ //試?yán)? <script setup> import { ref, onServerPrefetch, onMounted } from 'vue' const data = ref(null) onServerPrefetch(async () => { // 組件作為初始請求的一部分被渲染 // 在服務(wù)器上預(yù)抓取數(shù)據(jù),因?yàn)樗仍诳蛻舳松细臁? data.value = await fetchOnServer(/* ... */) }) onMounted(async () => { if (!data.value) { // 如果數(shù)據(jù)在掛載時(shí)為空值,這意味著該組件 // 是在客戶端動態(tài)渲染的。將轉(zhuǎn)而執(zhí)行 // 另一個(gè)客戶端側(cè)的抓取請求 data.value = await fetchOnClient(/* ... */) } }) </script>
九: 解決沒有this + 各種api的方法
- 在Vue2項(xiàng)目中可以使用this.$router.push等方法進(jìn)行路由的跳轉(zhuǎn),但是在Vue3的setup函數(shù)里,并沒有this這個(gè)概念,因此如何使用路由方法
// 在新的vue-router里面尤大加入了一些方法,比如這里代替this的useRouter,具體使用如下: //引入路由函數(shù) import { useRouter } from "vue-router"; //使用 setup() { //初始化路由 const router = useRouter(); router.push({ path: "/" }); return {}; }
- 在vue2中可以通過this來訪問到$refs,vue3中由于沒有this所以獲取不到了,但是官網(wǎng)中提供了方法來獲取:
<template> <h2 ref="root">姓名</h2> </template> <script> //使用setup的注意事項(xiàng) import { onMounted, ref } from 'vue' export default { name: 'test9', setup(){ const root = ref(null) onMounted(()=>{ console.log(root.value); }) return { root } }, } </script> //第二種方法,也可以通過getCurrentInstance來獲取 <template> <h2 ref="root">姓名</h2> </template> <script> //使用setup的注意事項(xiàng) import { onMounted, ref, getCurrentInstance } from 'vue' export default { name: 'test9', setup(){) const {proxy} = getCurrentInstance() onMounted(()=>{ console.log(proxy.$refs.root); }) return { } }, } </script>
- 關(guān)于element在vue3的使用方法,沒有this.$message等方法解決方案
//關(guān)于element在vue3的使用方法,沒有this.$message等方法解決方案 <template> <!-- 測試組件 --> <button @click="doLogin">登錄</button> </template> <script> import { getCurrentInstance } from 'vue' export default { name: 'Test', setup () { const instance = getCurrentInstance() // vue3提供的方法,創(chuàng)建類似于this的實(shí)例 const doLogin = () => { instance.proxy.$message({ type: 'error', text: '登錄失敗' }) // 類似于this.$message() } return { doLogin } }, // 如果想試用this.$message,須在mounted鉤子函數(shù)中,setup中沒有this實(shí)例, //但vue3.0中還是建議在setup函數(shù)中進(jìn)行邏輯操作 mounted () { this.$message({ type: 'error', text: '登錄失敗' }) } } </script>
相關(guān)文章
vue-cli3 karma單元測試的實(shí)現(xiàn)
這篇文章主要介紹了vue-cli3 karma單元測試的實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-01-01Vue3實(shí)現(xiàn)獲取驗(yàn)證碼按鈕倒計(jì)時(shí)效果
這篇文章主要介紹了Vue3實(shí)現(xiàn)獲取驗(yàn)證碼按鈕倒計(jì)時(shí)效果,用戶點(diǎn)擊獲取驗(yàn)證碼按鈕,發(fā)送請求給后端,按鈕失效,并且開始倒計(jì)時(shí)60秒;在此期間,用戶無法再次點(diǎn)擊按鈕,即使用戶刷新頁面,倒計(jì)時(shí)依然存在,直到倒計(jì)時(shí)完畢,按鈕恢復(fù),感興趣的小伙伴跟著小編一起來看看吧2024-10-10VUE項(xiàng)目中加載已保存的筆記實(shí)例方法
在本篇文章里小編給大家整理了一篇關(guān)于VUE項(xiàng)目中加載已保存的筆記實(shí)例方法,有興趣的讀者們可以參考下。2019-09-09vue解析Json數(shù)據(jù)獲取Json里面的多個(gè)id問題
這篇文章主要介紹了vue解析Json數(shù)據(jù)獲取Json里面的多個(gè)id問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12Vue3視頻播放器組件Vue3-video-play新手入門教程
這篇文章主要給大家介紹了關(guān)于Vue3視頻播放器組件Vue3-video-play新手入門教程的相關(guān)資料,本文實(shí)例為大家分享了vue-video-player視頻播放器的使用配置,供大家參考,需要的朋友可以參考下2023-12-12Vue實(shí)現(xiàn)項(xiàng)目部署到非根目錄及解決刷新頁面時(shí)找不到資源
這篇文章主要介紹了Vue實(shí)現(xiàn)項(xiàng)目部署到非根目錄及解決刷新頁面時(shí)找不到資源問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03