vue3的基本使用2
1、hook
Vue3 的 hook函數 相當于 vue2 的 mixin,不同在于 hook 是函數,其使用目的是為了復用代碼,讓setup中的邏輯更加清楚易懂。
使用示例:
在 src 目錄下建立一個 hooks 文件夾,聲明一個用于存放需要復用的代碼的 js 文件,如下:

文件內容如下:
import {reactive,onMounted,onBeforeUnmount} from 'vue'
export default function (){
//實現鼠標“打點”相關的數據
let point = reactive({
x:0,
y:0
})
//實現鼠標“打點”相關的方法
function savePoint(event){
point.x = event.pageX
point.y = event.pageY
console.log(event.pageX,event.pageY)
}
//實現鼠標“打點”相關的生命周期鉤子
onMounted(()=>{
window.addEventListener('click',savePoint)
})
onBeforeUnmount(()=>{
window.removeEventListener('click',savePoint)
})
return point
}
上面的自定義 hook 實際上相當于封裝了一個給頁面綁定點擊事件,并且記錄了點擊事件的鼠標位置的方法,可復用于各個組件之間。
在組件中引入并使用自定義 hook ,代碼如下:
<template> <h2>當前求和為:{{sum}}</h2> <button @click="sum++">點我+1</button> <hr> <h2>當前點擊時鼠標的坐標為:x:{{point.x}},y:{{point.y}}</h2> </template> <script> import {ref} from 'vue' import usePoint from '../hooks/usePoint' export default { name: 'Demo', setup(){ //數據 let sum = ref(0) let point = usePoint() //返回一個對象(常用) return {sum,point} } } </script>
由此上面組件即復用了自定義 hook 的方法,給頁面綁定了點擊事件,并且記錄了點擊的鼠標。自定義hook的作用類似于vue2中的mixin技術,自定義Hook的優勢在于很清楚復用功能代碼的來源,更清楚易懂。
2、響應性API之基礎api
2.1、toRaw()(將響應式對象轉為普通對象)
toRaw() 可以將一個由reactive生成的響應式對象轉為普通對象,該方法返回 reactive 或 readonly 代理的原始對象。
const foo = {}
const reactiveFoo = reactive(foo)
console.log(toRaw(reactiveFoo) === foo) // true
使用場景:用于讀取響應式對象對應的普通對象,該普通對象不具有響應式特性,對這個普通對象的所有操作,不會引起頁面更新。
2.2、markRow()(標記對象使其無法成為響應式)
作用:可用于標記一個對象,此時該對象將永遠無法成為響應式對象,該方法返回該對象本身。當某個對象我們并不想讓其具有響應式特性時,可以使用該方法對該對象進行標記。
應用場景:
- 有些值不應被設置為響應式的,例如復雜的第三方類庫等。
- 當渲染具有不可變數據源的大列表時,跳過響應式轉換可以提高性能。
示例:
setup(){
//數據
let person = reactive({
name:'張三',
age:18,
job:{
j1:{
salary:20
}
}
})function addCar(){
let car = {name:'奔馳',price:40}
person.car = markRaw(car)
}//返回一個對象(常用)
return {
person,
addCar
}
}
上面 person.car 并不具有響應式特性,修改 person.car 的值頁面不會發生改變。
2.3、響應式數據的判斷(isRef、isReactive、isReadonly、isProxy)
- isRef: 檢查一個值是否為一個 ref 對象
- isReactive: 檢查一個對象是否是由
reactive創建的響應式代理 - isReadonly: 檢查一個對象是否是由
readonly創建的只讀代理 - isProxy: 檢查一個對象是否是由
reactive或者readonly方法創建的代理
3、響應性API之Refs
3.1、toRef() 函數
toRef() 函數用來用來為源響應式對象上的某個 property 創建一個 ref 對象,其 value 值指向該響應式對象中的屬性值。新創建的 ref 對象會保持對其源對象的對應 property 值的響應式連接。一般來說,當我們需要將響應式對象中的某個屬性單獨提供給外部使用時就可以使用 toRef() 函數。
比如下面的 person,如果直接返回 person.name,則該屬性并不是響應式的,無法響應式地綁定在頁面上。我們可以通過類似于 ref(person, 'name') 的寫法來為響應式對象的某個屬性創建 ref 對象,頁面通過綁定該 ref 對象可以實現雙向綁定的效果。
<template> <h4>{{person}}</h4> <h2>姓名:{{name2}}</h2> <h2>年齡:{{age}}</h2> <button @click="name2+='~'">修改姓名</button> <button @click="age++">增長年齡</button> </template> <script> import {ref,reactive,toRef,toRefs} from 'vue' export default { name: 'Demo', setup(){ //數據 let person = reactive({ name:'張三', age:18, job:{ j1:{ salary:20 } } }) const name2 = toRef(person,'name')//返回一個對象(常用) return { person, name2:name2, age:toRef(person,'age'), salary:toRef(person.job.j1,'salary') } } } </script>
3.2、toRefs() 函數
toRefs 與toRef功能一致,不過 roRefs() 是一次性批量創建多個 ref 對象,并且返回的是一個對象。語法:toRefs(person)
代碼示例:
<template> <h4>{{person}}</h4> <h2>薪資:{{job.j1.salary}}K</h2> <button @click="job.j1.salary++">漲薪</button> </template> <script> import {ref,reactive,toRef,toRefs} from 'vue' export default { name: 'Demo', setup(){ //數據 let person = reactive({ name:'張三', age:18, job:{ j1:{ salary:20 } } })//返回一個對象(常用) return { person, ...toRefs(person) } } } </script>
3.3、customRef()(自定義ref)
作用:創建一個自定義的 ref,并對其依賴項跟蹤和更新觸發進行顯式控制。該方法接收一個工廠函數,該工廠函數接收 track 和 trigger 函數作為參數,并且應該返回一個帶有 get 和 set 的對象。
示例:
通過 customRef() 可以自定義一個 ref,在響應式過程中執行一些自定義操作。
<template> <input type="text" v-model="keyWord"> <h3>{{keyWord}}</h3> </template> <script> import {ref,customRef} from 'vue' export default { name: 'App', setup() { //自定義一個ref——名為:myRef function myRef(value, delay){ let timer return customRef((track,trigger)=>{ return { get(){ console.log(`有人從myRef這個容器中讀取數據了,我把${value}給他了`) track() //通知Vue追蹤value的變化(提前和get商量一下,讓他認為這個value是有用的) return value }, set(newValue){ console.log(`有人把myRef這個容器中數據改為了:${newValue}`) clearTimeout(timer) timer = setTimeout(()=>{ value = newValue trigger() //通知Vue去重新解析模板 }, delay) }, } }) } // let keyWord = ref('hello') //使用Vue提供的ref let keyWord = myRef('hello',500) //使用程序員自定義的ref return {keyWord} } } </script>
4、其他組合式api
shallowReactive(obj)(淺響應式):只有對象的最頂層屬性有響應式。也就是說,如果修改對象的屬性不是頂層的屬性,則并不會有響應式的效果。
shallowRef():只處理基本數據類型的響應式,不進行對象的響應式處理。也就是說,該方法只接收基本數據類型的對象,如果是參數是對象類型,則返回的對象不具有響應式。
readonly()(深只讀):接受一個對象 (可以是響應式對象或純對象) 或 ref 并返回原始對象的只讀代理。返回的只讀對象如果對該對象屬性進行修改,瀏覽器會報錯。
shallowReadonly()(淺只讀):跟 readonly() 一樣,接受一個對象 (可以是響應式對象或純對象) 或 ref 并返回原始對象的只讀代理。但是該方法只是控制淺只讀,也就是可以修改返回的代理對象的非頂層屬性的值,修改后也有響應式的效果。
5、provide/inject
父子組件之間可以通過 props 來傳遞數據,但是如果是更深層級的,如果仍然將 prop 沿著組件鏈逐級傳遞下去會很麻煩。對于這種情況,我們可以使用一對 provide 和 inject。父組件通過 provide 選項來提供數據,子組件通過 inject 選項來開始使用這些數據。不管兩個組件之間的層級有多深(不管是父子組件,還是祖孫組件或者更深層級),都可以通過 provide/inject 來進行數據傳遞。

// 父組件提供數據 setup(){ ...... let car = reactive({name:'奔馳',price:'40萬'}) provide('car',car) ...... } //后代組件使用數據 setup(props,context){ ...... const car = inject('car') return {car} ...... }
代碼示例:
假設有父組件 app.vue,子組件 child.vue,孫組件 son.vue,實現組件之間數據傳遞如下:

父組件 app.vue 代碼如下:
<template> <div class="app"> <h3>我是App組件(祖),{{name}}--{{price}}</h3> <Child/> </div> </template> <script> import { reactive,toRefs,provide } from 'vue' import Child from './components/Child.vue' export default { name:'App', components:{Child}, setup(){ let car = reactive({name:'奔馳',price:'40W'}) provide('car',car) //給自己的后代組件傳遞數據 return {...toRefs(car)} } } </script> <style> .app{ background-color: gray; padding: 10px; } </style>
子組件 child.vue 代碼如下:
<template> <div class="child"> <h3>我是Child組件(子)</h3> <Son/> </div> </template> <script> import {inject} from 'vue' import Son from './Son.vue' export default { name:'Child', components:{Son}, setup(){ let x = inject('car') console.log(x,'Child-----') } } </script> <style> .child{ background-color: skyblue; padding: 10px; } </style>
孫組件代碼如下:
<template> <div class="son"> <h3>我是Son組件(孫),{{car.name}}--{{car.price}}</h3> </div> </template> <script> import {inject} from 'vue' export default { name:'Son', setup(){ let car = inject('car') return {car} } } </script> <style> .son{ background-color: orange; padding: 10px; } </style>
6、vue3中的新組件
6.1、Fragment組件
在Vue2中組件必須有一個根標簽,但是在 Vue3 中組件是可以沒有根標簽的,當組件沒有根標簽時,vue3 內部會自動將多個標簽包含在一個Fragment虛擬元素中,該元素并不會渲染在頁面上,只是通過 vue 開發者工具可以看到。該組件的作用就是可以讓 vue3 的組件省略根標簽,減少標簽層級,減小內存占用。
示例:
<template> <div> <img alt="Vue logo" src="./assets/logo.png"> <Demo /> </div> </template> <script> import Demo from './components/Demo'; export default { name: 'App', components: { Demo, }, }; </script> <style> </style>
上面代碼為 app.vue,在該組件中使用了根標簽,而假設我們在引入的 demo 組件中并沒有使用根標簽,則可以看到效果如下:

6.2、Teleport組件
Teleport 是一種能夠將組件的 html 結構移動到指定位置的技術。
比如說我們想要在一個組件里面實現彈窗和遮罩層效果,但是在使用該組件時,可能層級很深,導致很難實現那種需要以 body 或者 html 作為直接父元素來實現的樣式,比如想要實現全屏居中,或者遮罩全屏的效果,在使用該組件后,由于該組件可能此時處于很深的層級下,導致很難實現實現全屏居中或者遮罩的效果。此時我們可以使用 Teleport 組件,通過該組件可以指定一些 html 在渲染后直接移動到指定的位置下。
使用語法:
<teleport to="移動位置"> <!-- 比如可以指定body或者指定id,如 to="body"、to="html"、to="#myId" --> <div> <p></p> ... </div> </teleport>
代碼示例:
如下,在 app.vue 中使用 dialog 組件,dialog 組件通過 Teleport 組件在渲染后將指定的 html 直接移動至 body 下。
app.vue 代碼如下:
<template> <div class="app"> <h3>我是App組件</h3> <Dialog/> </div> </template> <script> import Dialog from './Dialog.vue' export default { name:'App', components:{Dialog}, } </script> <style> .app{ background-color: gray; padding: 10px; } </style>
dialog 組件代碼如下:
<template> <div> <button @click="isShow = true">點我彈個窗</button> <teleport to="body"> <div v-if="isShow" class="mask"> <div class="dialog"> <h3>我是一個彈窗</h3> <h4>一些內容</h4> <h4>一些內容</h4> <h4>一些內容</h4> <button @click="isShow = false">關閉彈窗</button> </div> </div> </teleport> </div> </template> <script> import {ref} from 'vue' export default { name:'Dialog', setup(){ let isShow = ref(false) return {isShow} } } </script> <style> </style>
在顯示彈出框的 mask 元素后,可以看到該元素直接插入到了 body 下,此時要想設置該元素的一些需要基于 body 才好實現的樣式就會更加方便。

7、vue3 中的一些改變
7.1、全局 API 的轉移
Vue 2.x 有許多全局 API 和配置。
-
例如:注冊全局組件、注冊全局指令等。
//注冊全局組件 Vue.component('MyButton', { data: () => ({ count: 0 }), template: '<button @click="count++">Clicked {{ count }} times.</button>' }) //注冊全局指令 Vue.directive('focus', { inserted: el => el.focus() }
Vue3.0中對這些API做出了調整:
-
將全局的API,即:
Vue.xxx調整到應用實例(app)上
2.x 全局 API(Vue) | 3.x 實例 API (app) |
|---|---|
| Vue.config.xxxx | app.config.xxxx |
| Vue.config.productionTip | 移除 |
| Vue.component | app.component |
| Vue.directive | app.directive |
| Vue.mixin | app.mixin |
| Vue.use | app.use |
| Vue.prototype | app.config.globalProperties |
7.2、其他改變
- data選項應始終被聲明為一個函數。
- 過度類名的更改:
Vue2.x寫法:
.v-enter, .v-leave-to { opacity: 0; } .v-leave, .v-enter-to { opacity: 1; }
Vue3.x寫法:
.v-enter-from, .v-leave-to { opacity: 0; } .v-leave-from, .v-enter-to { opacity: 1; }
-
移除keyCode作為 v-on 的修飾符,同時也不再支持
config.keyCodes -
移除
v-on.native修飾符
vue3中通過該事件是否在子組件中的 emits 選項中來判斷該事件是否為自定義事件,是則為自定義事件,否則為原生事件。
父組件中綁定事件:
<my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" />
子組件中聲明自定義事件:
<script> export default { emits: ['close'] } </script>
- 移除過濾器(filter)。官方說法:過濾器雖然這看起來很方便,但它需要一個自定義語法,打破大括號內表達式是 “只是 JavaScript” 的假設,這不僅有學習成本,而且有實現成本!建議用方法調用或計算屬性去替換過濾器。
10、vue3的響應式原理
<script>
//目標對象
let obj = {
name: "張三",
age: 12,
bobby: ['釣魚', '唱k']
};
//proxy把目標對象轉化成代理對象
//參數1:目標對象 參數2:處理器對象,用來監聽數據,及操作數據
let proxyObj = new Proxy(obj, {
//監聽取值,第一個參數目標對象,第二個參數被獲取的屬性名
get(target, prop) {
console.log('觸發了get操作');
// return target[prop];//不推薦
//使用Reflect為了優化Object的一些操作方法以及合理的返回Object操作返回的結果
return Reflect.get(target, prop);
},
//監聽設置值
set(target, prop, val) {
console.log('觸發了set操作');
// target[prop]=val;//不推薦
return Reflect.set(target, prop, val);
},
//監聽刪除delete
deleteProperty(target, prop) {
console.log('觸發了delete操作');
// delete target[prop];不推薦
return Reflect.deleteProperty(target, prop);
}
});
console.log('初始代理對象值', proxyObj);
proxyObj.name = "李四"; //修改屬性值
console.log('修改屬性值后代理對象值', proxyObj, '修改后原對象值', obj);
proxyObj.sex = '男' //增加屬性值
console.log('增加屬性值后代理對象值', proxyObj, '修改后原對象值', obj);
proxyObj.bobby[0] = '踢球' //直接修改數組索引值
console.log('修改數組索引值后代理對象值', proxyObj.bobby[0], '修改后原對象值', obj.bobby[0]);
delete proxyObj.age; //刪除屬性值
console.log('刪除屬性后原對象值', obj);
</script>
上面通過 proxy 對 obj 對象的屬性值的讀寫、添加、刪除操作進行劫持,對于原對象的屬性的增刪改查操作就能監聽得到,也就可以進行后面的一系列操作,以此來作為實現響應式的基礎。
并且通過 proxy 代理來劫持對象屬性,可以避免 vue2 中通過 Object.defineProperty() 無法監聽對象屬性的添加和刪除操作,無法監聽數組中直接通過索引來修改數組值的弊端。
執行結果如下:

(上面的代碼不用 Reflect 直接通過返回或者設置原對象的屬性實際也可以,Reflect 這個 API 在 es6 中被提出,目的主要是為了形成一個未來規范吧,把操作對象相關的方法統一到 Reflect。)
9.1、Proxy
proxy 可以理解成,在目標對象之前架設一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。Proxy 這個詞的原意是代理,用在這里表示由它來“代理”某些操作,可以譯為“代理器”。
ES6 原生提供 Proxy 構造函數,用來生成 Proxy 實例。
var proxy = new Proxy(target, handler);

浙公網安備 33010602011771號