2022-10-24

一、Vue3.0 環(huán)境搭建

使用 vite 創(chuàng)建 Vue(3.2.30)項(xiàng)目

npm install yarn -gyarn create vite vue3-project --template vuecdvue3-project // 進(jìn)入項(xiàng)目根目錄yarn // 安裝依賴包yarn dev // 啟動(dòng)本地服務(wù)

安裝 vue-router、vuex全家桶

yarn add vue-router@latest // v4.0.14

yarn add vuex@latest // v4.0.2

安裝 UI 組件庫(kù):在Vue3環(huán)境中,一定找支持 Vue3的組件庫(kù),那些 Vue2的組件庫(kù)是無(wú)法使用的。

yarn add ant-design-vue@next // v2.2.8

yarn add vite-plugin-components --dev // 支持ant-design-vue按需引入

支持 ant-design-vue 組件按需引入

#vite.config.tsimport{defineConfig}from'vite'importvuefrom'@vitejs/plugin-vue'importViteComponents,{AntDesignVueResolver}from'vite-plugin-components'// https://vitejs.dev/config/exportdefaultdefineConfig({plugins:[vue(),ViteComponents({customComponentResolvers:[AntDesignVueResolver()],})]})

支持 Sass 樣式語(yǔ)法

yarn add sass // v1.49.9

1、入口文件 main.js

import{createApp}from'vue'importrouterfrom'./router.ts'importstorefrom'./store'importAppfrom'./App.vue'// 導(dǎo)入U(xiǎn)I樣式表import"ant-design-vue/dist/antd.css"constapp=createApp(App)// 配置全局屬性(這里不能再使用Vue.prototype了)app.config.globalProperties.$http=''app.use(router)// 注冊(cè)路由系統(tǒng)app.use(store)// 注冊(cè)狀態(tài)管理// 全局指令app.directive('highlight',{beforeMount(el,binding,vnode){el.style.background=binding.value}})app.mount('#app')// 掛載

2、Vue-Router (v4) 詳解

注意:在vue3環(huán)境中,必須要使用vue-router(v4)

創(chuàng)建router,使用createRouter()

指定路由模式,使用history屬性:createWebHashHistory/createWebHistory()

路由注冊(cè),在mian.js中 app.use(router)

如果當(dāng)前項(xiàng)目嚴(yán)格使用組合式API進(jìn)行開發(fā),必須使用 useRoute、userRouter等Hooks API進(jìn)行開發(fā)。

<router-link>已經(jīng)沒有tag屬性的,可以用custom和插槽實(shí)現(xiàn)自定義。

<router-view>新增了"插槽"功能,極其強(qiáng)大,參見路由中的偽代碼,它在實(shí)現(xiàn)<keep-alive>和<transition>動(dòng)畫將變得更簡(jiǎn)單,還可以Suspense實(shí)現(xiàn)Loading。

新增了幾個(gè)組合API:useRoute/useRouter/useLink。

查詢vue-router(v3)和vue-router(v4)的變化:https://next.router.vuejs.org/zh/guide/migration/index.html

在Vue3環(huán)境中編寫路由配置,參考代碼如下:

import{createRouter,createWebHashHistory}from'vue-router'constHome=()=>import('@/pages/study/Home.vue')constFind=()=>import('@/pages/study/Find.vue')constUser=()=>import('@/pages/study/User.vue')constCnode=()=>import('@/pages/cnode/index.vue')exportdefaultcreateRouter({history:createWebHashHistory(),// Hash路由routes:[{path:'/home',component:Home,meta:{transition:'fade',isAlive:true}},{path:'/find',component:Find},{path:'/user',component:User},{path:'/cnode',component:Cnode}]})

3、Vuex 根store 代碼示例

版本:在vue3環(huán)境中,必須要使用 vuex(4)

注意:在組件中使用 vuex數(shù)據(jù)時(shí),哪怕是在setup中,也要使用 computed 來(lái)訪問(wèn)狀態(tài)管理中的數(shù)據(jù),否則數(shù)據(jù)不響應(yīng)。

在Vue3環(huán)境中編寫 Vuex代碼示例如下:

#src/store/index.tsimport{createStore}from'vuex'importcnodefrom'./modules/cnode'exportdefaultcreateStore({getters:{},modules:{cnode}})

4、Vuex 子store 代碼示例

#src/store/modules/cnode.tsimport{fetchList}from'@/utils/api'exportdefault{namespaced:true,state:{msg:'cnode',list:[],cates:[{id:1,tab:'',label:'全部'},{id:2,tab:'good',label:'精華'},{id:3,tab:'share',label:'分享'},{id:4,tab:'ask',label:'問(wèn)答'},{id:5,tab:'job',label:'招聘'}]},mutations:{updateList(state,payload){state.list=payload}},actions:{getList({commit},params){fetchList(params).then(list=>{console.log('文章列表',list)commit('updateList',list)})}}}

5、App 根組件代碼示例

<template><!-- 路由菜單 --><router-linkto='/home'>首頁(yè)</router-link><router-linkto='/find'>發(fā)現(xiàn)</router-link><router-linkto='/user'>我們</router-link><!-- 視圖容器 --><router-view/></template><scriptsetup></script><stylelang='scss'>html,body{padding:0;margin:0;}</style><stylelang='scss'scoped>a{display:inline-block;padding:5px15px;}</style>

二、組合API 詳解

為什么要使用setup組合?

Vue3 中新增的 setup,目的是為了解決 Vue2 中“數(shù)據(jù)和業(yè)務(wù)邏輯不分離”的問(wèn)題。

Vue3中使用 setup 是如何解決這一問(wèn)題的呢?

第1步: 用setup組合API 替換 vue2 中的data/computed/watch/methods等選項(xiàng);

第2步: 把setup中相關(guān)聯(lián)的功能封裝成一個(gè)個(gè)可獨(dú)立可維護(hù)的hooks。

1、ref

作用:一般用于定義基本數(shù)據(jù)類型數(shù)據(jù),比如 String / Boolean / Number等。

背后:ref 的背后是使用 reactive 來(lái)實(shí)現(xiàn)的響應(yīng)式.

語(yǔ)法:const x = ref(100)

訪問(wèn):在 setup 中使用 .value 來(lái)訪問(wèn)。

<template><h1v-text='num'></h1><!-- 在視圖模板中,無(wú)須.value來(lái)訪問(wèn) --><button@click='num--'>自減</button><!-- 在setup中,要使用.value來(lái)訪問(wèn) --><button@click='add'>自增</button></template><scriptsetup>import{ref}from'vue'constnum=ref(100)constadd=()=>num.value++</script>

2、isRef

作用:判斷一個(gè)變量是否為一個(gè) ref 對(duì)象。

語(yǔ)法:const bol = isRef(x)

<template><h1v-text='hello'></h1></template><scriptsetup>import{ref,isRef,reactive}from'vue'consthello=ref('Hello')constworld=reactive('World')console.log(isRef(hello))// trueconsole.log(isRef(world))// false</script>

3、unref

作用:用于返回一個(gè)值,如果訪問(wèn)的是 ref變量,就返回其 .value值;如果不是 ref變量,就直接返回。

語(yǔ)法:const x = unref(y)

<template><h1v-text='hello'></h1></template><scriptsetup>import{ref,unref}from'vue'consthello=ref('Hello')constworld='World'console.log(unref(hello))// 'Hello'console.log(unref(world))// 'World'</script>

4、customRef

作用:自定義ref對(duì)象,把ref對(duì)象改寫成get/set,進(jìn)一步可以為它們添加 track/trigger。

<template><h1v-text='num'></h1><button@click='num++'>自增</button></template><scriptsetup>import{customRef,isRef}from'vue'constnum=customRef((track,trigger)=>{letvalue=100return{get(){track()returnvalue},set(newVal){value=newValtrigger()}}})console.log(isRef(num))// true</script>

5、toRef

作用:把一個(gè) reactive對(duì)象中的某個(gè)屬性變成 ref 變量。

語(yǔ)法:const x = toRef(reactive(obj), 'key') // x.value

<template><h1v-text='age'></h1></template><scriptsetup>import{toRef,reactive,isRef}from'vue'letuser={name:'張三',age:10}letage=toRef(reactive(user),'age')console.log(isRef(age))// true</script>

6、toRefs

作用:把一個(gè)reactive響應(yīng)式對(duì)象變成ref變量。

語(yǔ)法:const obj1 = toRefs(reactive(obj))

應(yīng)用:在子組件中接收父組件傳遞過(guò)來(lái)的 props時(shí),使用 toRefs把它變成響應(yīng)式的。

<template><h1v-text='info.age'></h1></template><scriptsetup>import{toRefs,reactive,isRef}from'vue'letuser={name:'張三',age:10}letinfo=toRefs(reactive(user))console.log(isRef(info.age))// trueconsole.log(isRef(info.name))// trueconsole.log(isRef(info))// true</script>

7、shallowRef

作用:對(duì)復(fù)雜層級(jí)的對(duì)象,只將其第一層變成 ref 響應(yīng)。 (性能優(yōu)化)

語(yǔ)法:const x = shallowRef({a:{b:{c:1}}, d:2}) 如此a、b、c、d變化都不會(huì)自動(dòng)更新,需要借助 triggerRef 來(lái)強(qiáng)制更新。

<template><h1v-text='info.a.b.c'></h1><button@click='changeC'>更新[c]屬性</button><h1v-text='info.d'></h1><button@click='changeD'>更新[d]屬性</button></template><scriptsetup>import{shallowRef,triggerRef,isRef}from'vue'letinfo=shallowRef({a:{b:{c:1}},d:2})console.log(isRef(info.value.a.b.c))// falseconsole.log(isRef(info))// trueconsole.log(isRef(info.a))// falseconsole.log(isRef(info.d))// falseconstchangeC=()=>{info.value.a.b.c++triggerRef(info)// 強(qiáng)制渲染更新}constchangeD=()=>{info.value.d++triggerRef(info)// 強(qiáng)制渲染更新}</script>

8、triggerRef

作用:強(qiáng)制更新一個(gè) shallowRef對(duì)象的渲染。

語(yǔ)法:triggerRef(shallowRef對(duì)象)

參考代碼:見上例。

9、reactive

作用:定義響應(yīng)式變量,一般用于定義引用數(shù)據(jù)類型。如果是基本數(shù)據(jù)類型,建議使用ref來(lái)定義。

語(yǔ)法:const info = reactive([] | {})

<template><divv-for='(item,idx) in list'><spanv-text='idx'></span>-<spanv-text='item.id'></span>-<spanv-text='item.label'></span>-<spanv-text='item.tab'></span></div><button@click='addRow'>添加一行</button></template><scriptsetup>import{reactive}from'vue'constlist=reactive([{id:1,tab:'good',label:'精華'},{id:2,tab:'ask',label:'問(wèn)答'},{id:3,tab:'job',label:'招聘'},{id:4,tab:'share',label:'分享'}])constaddRow=()=>{list.push({id:Date.now(),tab:'test',label:'測(cè)試'})}</script>

10、readonly

作用:把一個(gè)對(duì)象,變成只讀的。

語(yǔ)法:const rs = readonly(ref對(duì)象 | reactive對(duì)象 | 普通對(duì)象)

<template><h1v-text='info.foo'></h1><button@click='change'>改變</button></template><scriptsetup>import{reactive,readonly}from'vue'constinfo=readonly(reactive({bar:1,foo:2}))constchange=()=>{info.foo++// target is readonly}</script>

11、isReadonly

作用: 判斷一個(gè)變量是不是只讀的。

語(yǔ)法:const bol = isReadonly(變量)

<scriptsetup>import{reactive,readonly,isReadonly}from'vue'constinfo=readonly(reactive({bar:1,foo:2}))console.log(isReadonly(info))// trueconstuser=readonly({name:'張三',age:10})console.log(isReadonly(user))// true</script>

12、isReactive

作用:判斷一變量是不是 reactive的。

注意:被 readonly代理過(guò)的 reactive變量,調(diào)用 isReactive 也是返回 true的。

<scriptsetup>import{reactive,readonly,isReactive}from'vue'constuser=reactive({name:'張三',age:10})constinfo=readonly(reactive({bar:1,foo:2}))console.log(isReactive(info))// trueconsole.log(isReactive(user))// true</script>

13、isProxy

作用:判斷一個(gè)變量是不是 readonly 或 reactive的。

<scriptsetup>import{reactive,readonly,ref,isProxy}from'vue'constuser=readonly({name:'張三',age:10})constinfo=reactive({bar:1,foo:2})constnum=ref(100)console.log(isProxy(info))// trueconsole.log(isProxy(user))// trueconsole.log(isProxy(num))// false</script>

14、toRaw

作用:得到返回 reactive變量或 readonly變量的"原始對(duì)象"。

語(yǔ)法::const raw = toRaw(reactive變量或readonly變量)

說(shuō)明:reactive(obj)、readonly(obj) 和 obj 之間是一種代理關(guān)系,并且它們之間是一種淺拷貝的關(guān)系。obj 變化,會(huì)導(dǎo)致reactive(obj) 同步變化,反之一樣。

<scriptsetup>import{reactive,readonly,toRaw}from'vue'constuu={name:'張三',age:10}constuser=readonly(uu)console.log(uu===user)// falseconsole.log(uu===toRaw(user))// trueconstii={bar:1,foo:2}constinfo=reactive(ii)console.log(ii===info)// falseconsole.log(ii===toRaw(info))// true</script>

15、markRaw

作用:把一個(gè)普通對(duì)象標(biāo)記成"永久原始",從此將無(wú)法再變成proxy了。

語(yǔ)法:const raw = markRaw({a,b})

<scriptsetup>import{reactive,readonly,markRaw,isProxy}from'vue'constuser=markRaw({name:'張三',age:10})constu1=readonly(user)// 無(wú)法再代理了constu2=reactive(user)// 無(wú)法再代理了console.log(isProxy(u1))// falseconsole.log(isProxy(u2))// false</script>

16、shallowReactive

作用:定義一個(gè)reactive變量,只對(duì)它的第一層進(jìn)行Proxy,,所以只有第一層變化時(shí)視圖才更新。

語(yǔ)法:const obj = shallowReactive({a:{b:9}})

<template><h1v-text='info.a.b.c'></h1><h1v-text='info.d'></h1><button@click='change'>改變</button></template><scriptsetup>import{shallowReactive,isProxy}from'vue'constinfo=shallowReactive({a:{b:{c:1}},d:2})constchange=()=>{info.d++// 只改變d,視圖自動(dòng)更新info.a.b.c++// 只改變c,視圖不會(huì)更新// 同時(shí)改變c和d,二者都更新}console.log(isProxy(info))// trueconsole.log(isProxy(info.d))// false</script>

17、shallowReadonly

作用:定義一個(gè)reactive變量,只有第一層是只讀的。

語(yǔ)法:const obj = shallowReadonly({a:{b:9}})

<template><h1v-text='info.a.b.c'></h1><h1v-text='info.d'></h1><button@click='change'>改變</button></template><scriptsetup>import{reactive,shallowReadonly,isReadonly}from'vue'constinfo=shallowReadonly(reactive({a:{b:{c:1}},d:2}))constchange=()=>{info.d++// d是讀的,改不了info.a.b.c++// 可以正常修改,視圖自動(dòng)更新}console.log(isReadonly(info))// trueconsole.log(isReadonly(info.d))// false</script>

18、computed

作用:對(duì)響應(yīng)式變量進(jìn)行緩存計(jì)算。

語(yǔ)法:const c = computed(fn / {get,set})

<template><divclass='page'><spanv-for='p in pageArr'v-text='p'@click='page=p':class='{"on":p===page}'></span></div><!-- 在v-model上使用computed計(jì)算屬性 --><inputv-model.trim='text'/><br>你的名字是:<spanv-text='name'></span></template><scriptsetup>import{ref,computed}from'vue'constpage=ref(1)constpageArr=computed(()=>{constp=page.valuereturnp>3?[p-2,p-1,p,p+1,p+2]:[1,2,3,4,5]})constname=ref('')consttext=computed({get(){returnname.value.split('-').join('')},// 支持計(jì)算屬性的setter功能set(val){name.value=val.split('').join('-')}})</script><stylelang='scss'scoped>.page{&>span{display:inline-block;padding:5px15px;border:1pxsolid#eee;cursor:pointer;}&>span.on{color:red;}}</style>

19、watch

作用:用于監(jiān)聽響應(yīng)式變量的變化,組件初始化時(shí),它不執(zhí)行。

語(yǔ)法:const stop = watch(x, (new,old)=>{}),調(diào)用stop() 可以停止監(jiān)聽。

語(yǔ)法:const stop = watch([x,y], ([newX,newY],[oldX,oldY])=>{}),調(diào)用stop()可以停止監(jiān)聽。

<template><h1v-text='num'></h1><h1v-text='usr.age'></h1><button@click='change'>改變</button><button@click='stopAll'>停止監(jiān)聽</button></template><scriptsetup>import{ref,reactive,watch,computed}from'vue'// watch監(jiān)聽ref變量、reactive變量的變化constnum=ref(1)constusr=reactive({name:'張三',age:1})constchange=()=>{num.value++usr.age++}conststop1=watch([num,usr],([newNum,newUsr],[oldNum,oldUsr])=>{// 對(duì)ref變量,newNum是新值,oldNum是舊值console.log('num',newNum===oldNum)// false// 對(duì)reactive變量,newUsr和oldUsr相等,都是新值console.log('usr',newUsr===oldUsr)// true})// watch還可以監(jiān)聽計(jì)算屬性的變化consttotal=computed(()=>num.value*100)conststop2=watch(total,(newTotal,oldTotal)=>{console.log('total',newTotal===oldTotal)// false})// 停止watch監(jiān)聽conststopAll=()=>{stop1();stop2()}</script>

20、watchEffect

作用:相當(dāng)于是 react中的 useEffect(),用于執(zhí)行各種副作用。

語(yǔ)法:const stop = watchEffect(fn),默認(rèn)其 flush:'pre',前置執(zhí)行的副作用。

watchPostEffect,等價(jià)于 watchEffect(fn, {flush:'post'}),后置執(zhí)行的副作用。

watchSyncEffect,等價(jià)于 watchEffect(fn, {flush:'sync'}),同步執(zhí)行的副作用。

特點(diǎn):watchEffect 會(huì)自動(dòng)收集其內(nèi)部響應(yīng)式依賴,當(dāng)響應(yīng)式依賴發(fā)變化時(shí),這個(gè)watchEffect將再次執(zhí)行,直到你手動(dòng) stop() 掉它。

<template><h1v-text='num'></h1><button@click='stopAll'>停止掉所有的副作用</button></template><scriptsetup>import{ref,watchEffect}from'vue'letnum=ref(0)// 等價(jià)于 watchPostEffectconststop1=watchEffect(()=>{// 在這里你用到了 num.value// 那么當(dāng)num變化時(shí),當(dāng)前副作用將再次執(zhí)行// 直到stop1()被調(diào)用后,當(dāng)前副作用才死掉console.log('---effect post',num.value)},{flush:'post'})// 等價(jià)于 watchSyncEffectconststop2=watchEffect(()=>{// 在這里你用到了 num.value// 那么當(dāng)num變化時(shí),當(dāng)前副作用將再次執(zhí)行// 直到stop2()被調(diào)用后,當(dāng)前副作用才死掉console.log('---effect sync',num.value)},{flush:'sync'})conststop3=watchEffect(()=>{// 如果在這里用到了 num.value// 你必須在定時(shí)器中stop3(),否則定時(shí)器會(huì)越跑越快!// console.log('---effect pre', num.value)setInterval(()=>{num.value++// stop3()},1000)})conststopAll=()=>{stop1()stop2()stop3()}</script>

21、生命周期鉤子

選項(xiàng)式的 beforeCreate、created,被setup替代了。setup表示組件被創(chuàng)建之前、props被解析之后執(zhí)行,它是組合式 API 的入口。

選項(xiàng)式的 beforeDestroy、destroyed 被更名為 beforeUnmount、unmounted。

新增了兩個(gè)選項(xiàng)式的生命周期 renderTracked、renderTriggered,它們只在開發(fā)環(huán)境有用,常用于調(diào)試。

在使用 setup組合時(shí),不建議使用選項(xiàng)式的生命周期,建議使用 on* 系列 hooks生命周期。

<template><h1v-text='num'></h1><button@click='num++'>自增</button></template><scriptsetup>import{ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted,onRenderTracked,onRenderTriggered,onActivated,onDeactivated,onErrorCaptured}from'vue'console.log('---setup')constnum=ref(100)// 掛載階段onBeforeMount(()=>console.log('---開始掛載'))onRenderTracked(()=>console.log('---跟蹤'))onMounted(()=>console.log('---掛載完成'))// 更新階段onRenderTriggered(()=>console.log('---觸發(fā)'))onBeforeUpdate(()=>console.log('---開始更新'))onUpdated(()=>console.log('---更新完成'))// 銷毀階段onBeforeUnmount(()=>console.log('---開始銷毀'))onUnmounted(()=>console.log('---銷毀完成'))// 與動(dòng)態(tài)組件有關(guān)onActivated(()=>console.log('---激活'))onDeactivated(()=>console.log('---休眠'))// 異常捕獲onErrorCaptured(()=>console.log('---錯(cuò)誤捕獲'))</script>

22、provide / inject

作用:在組件樹中自上而下地傳遞數(shù)據(jù).

語(yǔ)法:provide('key', value)

語(yǔ)法:const value = inject('key', '默認(rèn)值')

# App.vue<scriptsetup>import{ref,provide}from'vue'constmsg=ref('Hello World')// 向組件樹中注入數(shù)據(jù)provide('msg',msg)</script># Home.vue<template><h1v-text='msg'></h1></template><scriptsetup>import{inject}from'vue'// 消費(fèi)組件樹中的數(shù)據(jù),第二參數(shù)為默認(rèn)值constmsg=inject('msg','Hello Vue')</script>

23、getCurrentInstance

作用:用于訪問(wèn)內(nèi)部組件實(shí)例。請(qǐng)不要把它當(dāng)作在組合式 API 中獲取 this 的替代方案來(lái)使用。

語(yǔ)法:const app = getCurrentInstance()

場(chǎng)景:常用于訪問(wèn) app.config.globalProperties 上的全局?jǐn)?shù)據(jù)。

<scriptsetup>import{getCurrentInstance}from'vue'constapp=getCurrentInstance()// 全局?jǐn)?shù)據(jù),是不具備響應(yīng)式的。constglobal=app.appContext.config.globalPropertiesconsole.log('app',app)console.log('全局?jǐn)?shù)據(jù)',global)</script>

24、關(guān)于setup代碼范式(最佳實(shí)踐)

只使用 setup 及組合API,不要再使用vue選項(xiàng)了。

有必要封裝 hooks時(shí),建議把功能封裝成hooks,以便于代碼的可維護(hù)性。

能用 vite就盡量使用vite,能用ts 就盡量使用ts。

三、Vue3 組件通信

1、第一個(gè)組件(品類組件)

使用 setup 及組件API ,自定義封裝 Vue3 組件。

defineProps 用于接收父組件傳遞過(guò)來(lái)的自定義屬性。

defineEmits 用于聲明父組件傳遞過(guò)來(lái)的自定義事件。

useStore,配合 computed 實(shí)現(xiàn)訪問(wèn) Vuex中的狀態(tài)數(shù)據(jù)。

# 文件名 CnCate.vue<template><divclass='cates'><spanv-for='item in cates'v-text='item.label':class='{"on": tab===item.tab}'@click='change(item.tab)'></span></div></template><scriptsetup>import{defineProps,defineEmits,computed}from'vue'import{useStore}from'vuex'// 接收自定義屬性constprops=defineProps({tab:{type:String,default:''}})constemit=defineEmits(['update:tab'])// 從vuex中訪問(wèn)cates數(shù)據(jù)conststore=useStore()constcates=computed(()=>store.state.cnode.cates)constchange=(tab)=>{emit('update:tab',tab)// 向父組件回傳數(shù)據(jù)}</script><stylelang="scss"scoped>.cates{padding:5px20px;background-color:rgb(246,246,246);}.catesspan{display:inline-block;height:24px;line-height:24px;margin-right:25px;color:rgb(128,189,1);font-size:14px;padding:010px;cursor:pointer;}.catesspan.on{background-color:rgb(128,189,1);color:white;border-radius:3px;}</style>

2、第二個(gè)組件(分頁(yè)組件)

使用 toRefs 把 props 變成響應(yīng)式的。在Vue3中,默認(rèn)情況下 props是不具備響應(yīng)式的,即父組件中的數(shù)據(jù)更新了,在子組件中卻是不更新的。

使用 computed 實(shí)現(xiàn)動(dòng)態(tài)頁(yè)碼結(jié)構(gòu)的變化。

defineProps、defineEmits,分別用于接收父組件傳遞過(guò)來(lái)的自定義屬性、自定義事件。

# 文件名 CnPage.vue<template><divclass='pages'><span@click='prev'>&lt;&lt;</span><spanv-if='page>3'>...</span><spanv-for='i in pages'v-text='i':class='{"on":i===page}'@click='emit("update:page", i)'></span><span>...</span><span@click='emit("update:page", page+1)'>>></span></div></template><scriptsetup>import{defineProps,defineEmits,computed,toRefs}from'vue'letprops=defineProps({page:{type:Number,default:1}})const{page}=toRefs(props)constemit=defineEmits(['update:page'])constpages=computed(()=>{// 1? 1 2 3 4 5 ...// 2? 1 2 3 4 5 ...// 3? 1 2 3 4 5 ...// 4? ... 2 3 4 5 6 ...// n? ... n-2 n-1 n n+1 n+2 ...constv=page.valuereturnv<=3?[1,2,3,4,5]:[v-2,v-1,v,v+1,v+2]})constprev=()=>{if(page.value===1)alert('已經(jīng)是第一頁(yè)了')elseemit('update:page',page.value-1)}</script><stylelang="scss"scoped>.pages{line-height:50px;text-align:right;}.pages>span{cursor:pointer;display:inline-block;width:34px;height:30px;margin:0;line-height:30px;text-align:center;font-size:12px;border:1pxsolid#ccc;}.pages>span.on{background:rgb(128,189,1);color:white;}</style>

3、在父級(jí)組件中使用 自定義組件

v-model:tab='tab' 是 :tab 和 @update:tab 的語(yǔ)法糖簡(jiǎn)寫;

v-model:page='page' 是 :page 和 @update:page 的語(yǔ)法糖簡(jiǎn)寫;

使用 watch 監(jiān)聽品類和頁(yè)面的變化,然后觸發(fā)調(diào)接口獲取新數(shù)據(jù)。

# 文件名 Cnode.vue<template><divclass='app'><!-- <CnCate :tab='tab' @update:tab='tab=$event' /> --><CnCatev-model:tab='tab'/><!-- <CnPage :page='page' @update:page='page=$event' /> --><CnPagev-model:page='page'/></div></template><scriptsetup>import{ref,watch}from'vue'importCnCatefrom'./components/CnCate.vue'importCnPagefrom'./components/CnPage.vue'consttab=ref('')constpage=ref(1)conststop=watch([tab,page],()=>{console.log('當(dāng)品類或頁(yè)碼變化時(shí),調(diào)接口')})</script>

四、Hooks 封裝

1、為什么要封裝 Hooks ?

我們都知道,在Vue2中,在同一個(gè).vue組件中,當(dāng) data、methods、computed、watch 的體量較大時(shí),代碼將變得臃腫。為了解決代碼臃腫問(wèn)題,我們除了拆分組件外,別無(wú)它法。

在Vue3中,同樣存在這樣的問(wèn)題:當(dāng)我們的組件開始變得更大時(shí),邏輯關(guān)注點(diǎn)將越來(lái)越多,這會(huì)導(dǎo)致組件難以閱讀和理解。但是,在Vue3中,我們除了可以拆分組件,還可以使用 Hooks封裝來(lái)解決這一問(wèn)題。

所謂 Hooks封裝,就是把不同的邏輯關(guān)注點(diǎn)抽離出來(lái),以達(dá)到業(yè)務(wù)邏輯的獨(dú)立性。這一思路,也是Vue3 對(duì)比Vue2的最大亮點(diǎn)之一。

2、如何封裝 Hooks 呢?

在 setup 組合的開發(fā)模式下,把具體某個(gè)業(yè)務(wù)功能所用到的 ref、reactive、watch、computed、watchEffect 等,提取到一個(gè)以 use* 開頭的自定義函數(shù)中去。

封裝成 use* 開頭的Hooks函數(shù),不僅可以享受到封裝帶來(lái)的便利性,還有利于代碼邏輯的復(fù)用。Hooks函數(shù)的另一個(gè)特點(diǎn)是,被復(fù)用時(shí)可以保持作用域的獨(dú)立性,即,同一個(gè)Hooks函數(shù)被多次復(fù)用,彼此是不干擾的。

3、在哪些情況下需要封裝 Hooks呢?

我總結(jié)了兩種場(chǎng)景:一種是功能類Hooks,即為了邏輯復(fù)用的封裝;另一種是業(yè)務(wù)類Hooks,即為了邏輯解耦的封裝。下面我給兩組代碼,說(shuō)明這兩種使用場(chǎng)景。

4、示例:功能類 Hooks封裝

import{computed}from'vue'import{useRoute}from'vue-router'import{useStore}from'vuex'// 返回路由常用信息exportfunctionuseLocation(){constroute=useRoute()constpathname=route.fullPathreturn{pathname}}// 用于方便地訪問(wèn)Vuex數(shù)據(jù)exportfunctionuseSelector(fn){conststore=useStore()// Vuex數(shù)據(jù)要用computed包裹,處理響應(yīng)式問(wèn)題returncomputed(()=>fn(store.state))}// 用于派發(fā)actions的exportfunctionuseDispatch(){conststore=useStore()returnstore.dispatch}

5、示例:業(yè)務(wù)類 Hooks封裝

import{ref,computed,watch,watchEffect}from'vue'import{useDispatch,useSelector}from'@/hooks'// 業(yè)務(wù)邏輯封裝exportfunctionuseCnode(){lettab=ref('')// tab.valueletpage=ref(1)// page.valueconstdispatch=useDispatch()// 使用 store數(shù)據(jù)constcates=useSelector(state=>state.cnode.cates)constlist=useSelector(state=>state.cnode.list)// 用于處理list列表數(shù)據(jù)constnewList=computed(()=>{constresult=[]list.value.forEach(ele1=>{constcate=cates.value.find(ele2=>ele2.tab===ele1.tab)ele1['label']=ele1.top?'置頂':(ele1.good?'精華':(cate?.label||'問(wèn)答'))ele1['first']=tab.value===''result.push(ele1)})returnresult})// 相當(dāng)于react中useEffect(fn, [])// watchEffect,它可以自動(dòng)收集依賴項(xiàng)watchEffect(()=>{dispatch('cnode/getList',{tab:tab.value,limit:5,page:page.value})})// 當(dāng)品類發(fā)生變化時(shí),頁(yè)碼重置為第一頁(yè)watch(tab,()=>page.value=1)return[tab,page,newList]}

最后想說(shuō)的是,不能為了封裝Hooks而封裝。要看具備場(chǎng)景:是否有復(fù)用的價(jià)值?是否有利于邏輯的分離?是否有助提升代碼的可閱讀性和可維護(hù)性?

五、Vue3 新語(yǔ)法細(xì)節(jié)

1、在Vue2中,v-for 和 ref 同時(shí)使用,這會(huì)自動(dòng)收集 $refs。當(dāng)存在嵌套的v-for時(shí),這種行為會(huì)變得不明確且效率低下。在Vue3中,v-for 和 ref 同時(shí)使用,這不再自動(dòng)收集$refs。我們可以手動(dòng)封裝收集 ref 對(duì)象的方法,將其綁定在 ref 屬性上。

<template><divclass='grid'v-for="i in 5":ref='setRef'><spanv-for='j in 5'v-text='(i-1)*5+j':ref='setRef'></span></div></template><scriptsetup>import{onMounted}from'vue'// 用于收集ref對(duì)象的數(shù)組constrefs=[]// 定義手動(dòng)收集ref的方法constsetRef=el=>{if(el)refs.push(el)}onMounted(()=>console.log('refs',refs))</script><stylelang='scss'scoped>.grid{width:250px;height:50px;display:flex;text-align:center;line-height:50px;&>span{flex:1;border:1pxsolid#ccc;}}</style>

2、在Vue3中,使用 defineAsyncComponent 可以異步地加載組件。需要注意的是,這種異步組件是不能用在Vue-Router的路由懶加載中。

<scriptsetup>import{defineAsyncComponent}from'vue'// 異步加載組件constAsyncChild=defineAsyncComponent({loader:()=>import('./components/Child.vue'),delay:200,timeout:3000})</script>

3、Vue3.0中的 $attrs,包含了父組件傳遞過(guò)來(lái)的所有屬性,包括 class 和 style 。在Vue2中,$attrs 是接到不到 class 和 style 的。在 setup 組件中,使用 useAttrs() 訪問(wèn);在非 setup組件中,使用 this.$attrs /setupCtx.attrs 來(lái)訪問(wèn)。

<scriptsetup>// 在非setup組件中,使用this.$attrs/setupCtx.attrsimport{useAttrs}from'vue'constattrs=useAttrs()// 能夠成功訪問(wèn)到class和styleconsole.log('attrs',attrs)</script>

4、Vue3中,移除了 $children 屬性,要想訪問(wèn)子組件只能使用 ref 來(lái)實(shí)現(xiàn)了。在Vue2中,我們使用 $children 可以方便地訪問(wèn)到子組件,在組件樹中“肆意”穿梭。

5、Vue3中,使用 app.directive() 來(lái)定義全局指令,并且定義指令時(shí)的鉤子函數(shù)們也發(fā)生了若干變化。

app.directive('highlight',{// v3中新增的created(){},// 相當(dāng)于v2中的 bind()beforeMount(el,binding,vnode,prevVnode){el.style.background=binding.value},// 相當(dāng)于v2中的 inserted()mounted(){},// v3中新增的beforeUpdate(){},// 相當(dāng)于v2中的 update()+componentUpdated()updated(){},// v3中新增的beforeUnmount(){},// 相當(dāng)于v2中的 unbind()unmounted(){}})

6、data 選項(xiàng),只支持工廠函數(shù)的寫法,不再支持對(duì)象的寫法了。在Vue2中,創(chuàng)建 new Vue({ data }) 時(shí),是可以寫成對(duì)象語(yǔ)法的。

<script>import{createApp}from'vue'createApp({data(){return{msg:'Hello World'}}}).mount('#app')</script>

7、Vue3中新增了 emits 選項(xiàng)。在非<script setup>寫法中,使用 emits選項(xiàng) 接收父組件傳遞過(guò)來(lái)的自定義,使用 ctx.emit() / this.$emit() 來(lái)觸發(fā)事件。在<script setup>中,使用 defineEmits 來(lái)接收自定義事件,使用 defineProps 來(lái)接收自定義事件。

<template><h1v-text='count'@click='emit("update:count", count+1)'></h1></template><scriptsetup>import{defineProps,defineEmits}from'vue'// 接收父組件傳遞過(guò)來(lái)的自定義屬性constprops=defineProps({count:{type:Number,default:100}})// 接收父組件傳遞過(guò)來(lái)的自定義事件// emit 相當(dāng)于 vue2中的 this.$emit()constemit=defineEmits(['change','update:count'])</script>

8、Vue3中 移除了 $on / $off / $once 這三個(gè)事件 API,只保留了 $emit 。

9、Vue3中,移除了全局過(guò)濾器(Vue.filter)、移除了局部過(guò)濾器 filters選項(xiàng)。取而代之,你可以封裝自定義函數(shù)或使用 computed 計(jì)算屬性來(lái)處理數(shù)據(jù)。

10、Vue3 現(xiàn)在正式支持了多根節(jié)點(diǎn)的組件,也就是片段,類似 React 中的 Fragment。使用片段的好處是,當(dāng)我們要在 template 中添加多個(gè)節(jié)點(diǎn)時(shí),沒必要在外層套一個(gè) div 了,套一層 div 這會(huì)導(dǎo)致多了一層 DOM結(jié)構(gòu)。可見,片段 可以減少?zèng)]有必要的 DOM 嵌套。

<template><header>...</header><main>...</main><footer>...</footer></template>

11、函數(shù)式組件的變化:在Vue2中,要使用 functional 選項(xiàng)來(lái)支持函數(shù)式組件的封裝。在Vue3中,函數(shù)式組件可以直接用普通函數(shù)進(jìn)行創(chuàng)建。如果你在 vite 環(huán)境中安裝了 `@vitejs/plugin-vue-jsx` 插件來(lái)支持 JSX語(yǔ)法,那么定義函數(shù)式組件就更加方便了。

#Counter.tsxexportdefault(props,ctx)=>{// props是父組件傳遞過(guò)來(lái)的屬性// ctx 中有 attrs, emit, slotsconst{value,onChange}=propsreturn(<><h1>函數(shù)式組件</h1><h1onClick={()=>onChange(value+1)}>{value}</h1></>)}

12、Vue2中的Vue構(gòu)造函數(shù),在Vue3中已經(jīng)不能再使用了。所以Vue構(gòu)造函數(shù)上的靜態(tài)方法、靜態(tài)屬性,比如 Vue.use/Vue.mixin/Vue.prototype 等都不能使用了。在Vue3中新增了一套實(shí)例方法來(lái)代替,比如 app.use()等。

import{createApp}from'vue'importrouterfrom'./router'importstorefrom'./store'importAppfrom'./App.vue'constapp=createApp(App)// 相當(dāng)于 v2中的 Vue.prototypeapp.config.globalProperties.$http=''// 等價(jià)于 v2中的 Vue.useapp.use(router)// 注冊(cè)路由系統(tǒng)app.use(store)// 注冊(cè)狀態(tài)管理

13、在Vue3中,使用 getCurrentInstance 訪問(wèn)內(nèi)部組件實(shí)例,進(jìn)而可以獲取到 app.config 上的全局?jǐn)?shù)據(jù),比如 $route、$router、$store 和自定義數(shù)據(jù)等。這個(gè) API 只能在?setup?或?生命周期鉤子?中調(diào)用。

<script>// 把使用全局?jǐn)?shù)據(jù)的功能封裝成Hooksimport{getCurrentInstance}from'vue'functionuseGlobal(key){returngetCurrentInstance().appContext.config.globalProperties[key]}</script><scriptsetup>import{getCurrentInstance}from'vue'constglobal=getCurrentInstance().appContext.config.globalProperties// 得到 $route、$router、$store、$http ...使用自定義Hooks方法訪問(wèn)全局?jǐn)?shù)據(jù)const$store=useGlobal('$store')console.log('store',$store)</script>

14、我們已經(jīng)知道,使用 provide 和 inject 這兩個(gè)組合 API 可以組件樹中傳遞數(shù)據(jù)。除此之外,我們還可以應(yīng)用級(jí)別的 app.provide() 來(lái)注入全局?jǐn)?shù)據(jù)。在編寫插件時(shí)使用 app.provide() 尤其有用,可以替代app.config.globalProperties。

#main.tsconstapp=createApp(App)app.provide('global',{msg:'Hello World',foo:[1,2,3,4]})#在組件中使用<scriptsetup>import{inject}from'vue'constglobal=inject('global')</script>

15、在Vue2中,Vue.nextTick() / this.$nextTick 不能支持 Webpack 的 Tree-Shaking 功能的。在 Vue3 中的 nextTick ,考慮到了對(duì) Tree-Shaking 的支持。

<template><divv-html='content'></div></template><scriptsetup>import{ref,watchPostEffect,nextTick}from'vue'constcontent=ref('')watchPostEffect(()=>{content.value=`<div id='box'>動(dòng)態(tài)HTML字符串</div>`// 在nextTick中訪問(wèn)并操作DOMnextTick(()=>{constbox=document.getElementById('box')box.style.color='red'box.style.textAlign='center'})})</script>

16、Vue3中,對(duì)于?v-if/v-else/v-else-if的各分支項(xiàng),無(wú)須再手動(dòng)綁定 key了, Vue3會(huì)自動(dòng)生成唯一的key。因此,在使用過(guò)渡動(dòng)畫 對(duì)多個(gè)節(jié)點(diǎn)進(jìn)行顯示隱藏時(shí),也無(wú)須手動(dòng)加 key了。

<template><!-- 使用<teleport>組件,把a(bǔ)nimate.css樣式插入到head標(biāo)簽中去 --><teleportto='head'><linkrel="stylesheet"/></teleport><!-- 使用<transition>過(guò)渡動(dòng)畫,無(wú)須加key了 --><transitionenter-active-class='animate__animated animate__zoomIn'leave-active-class='animate__animated animate__zoomOutUp'mode='out-in'><h1v-if='bol'>不負(fù)當(dāng)下</h1><h1v-else>不畏未來(lái)</h1></transition><button@click='bol=!bol'>Toggle</button></template><scriptsetup>import{ref}from'vue'constbol=ref(true)</script>

17、在Vue2中,使用 Vue.config.keyCodes 可以修改鍵盤碼,這在Vue3 中已經(jīng)淘汰了。

18、Vue3中,$listeners 被移除了。因此我們無(wú)法再使用 $listeners 來(lái)訪問(wèn)、調(diào)用父組件給的自定義事件了。

19、在Vue2中,根組件掛載 DOM時(shí),可以使用 el 選項(xiàng)、也可以使用 $mount()。但,在 Vue3中只能使用 $mount() 來(lái)掛載了。并且,在 Vue 3中,被渲染的應(yīng)用會(huì)作為子元素插入到 <div id='app'> 中,進(jìn)而替換掉它的innerHTML。

20、在Vue2中,使用 propsData 選項(xiàng),可以實(shí)現(xiàn)在 new Vue() 時(shí)向根組件傳遞 props 數(shù)據(jù)。在Vue3中,propsData 選項(xiàng) 被淘汰了。替代方案是:使用createApp的第二個(gè)參數(shù),在 app實(shí)例創(chuàng)建時(shí)向根組件傳入 props數(shù)據(jù)。

#main.tsimport{createApp}from'vue'importAppfrom'./App.vue'// 使用第二參數(shù),向App傳遞自定義屬性constapp=createApp(App,{name:'vue3'})app.mount('#app')// 掛載#App.vue<scriptsetup>import{defineProps}from'vue'// 接收 createApp() 傳遞過(guò)來(lái)的自定義屬性constprops=defineProps({name:{type:String,default:''}})console.log('app props',props)</script>

21、在Vue2中,組件有一個(gè) render 選項(xiàng)(它本質(zhì)上是一個(gè)渲染函數(shù),這個(gè)渲染函數(shù)的形參是 h 函數(shù)),h 函數(shù)相當(dāng)于 React 中的 createElement()。在Vue3中,render 函數(shù)選項(xiàng)發(fā)生了變化:它的形參不再是 h 函數(shù)了。h 函數(shù)變成了一個(gè)全局 API,須導(dǎo)入后才能使用。

import{createApp,h}from'vue'importAppfrom'./App.vue'constapp=createApp({render(){returnh(App)}},{name:'vue3'})app.$mount('#app')

22、Vue3中新增了實(shí)驗(yàn)性的內(nèi)置組件 <suspense>,它類似 React.Suspense 一樣,用于給異步組件加載時(shí),指定 Loading指示器。需要注意的是,這個(gè)新特征尚未正式發(fā)布,其 API 可能隨時(shí)會(huì)發(fā)生變動(dòng)。

<template><suspense><!-- 用name='default'默認(rèn)插槽加載異步組件 --><AsyncChild/><!-- 異步加載成功前的loading 交互效果 --><template#fallback><div>Loading...</div></template></suspense></template><scriptsetup>import{defineAsyncComponent}from'vue'constAsyncChild=defineAsyncComponent({loader:()=>import('./components/Child.vue'),delay:200,timeout:3000})</script>

23、Vue3中,過(guò)渡動(dòng)畫<transition>發(fā)生了一系列變化。之前的 v-enter 變成了現(xiàn)在的 v-enter-from , 之前的 v-leave 變成了現(xiàn)在的 v-leave-from 。另一個(gè)變化是:當(dāng)使用<transition>作為根結(jié)點(diǎn)的組件,從外部被切換時(shí)將不再觸發(fā)過(guò)渡效果。

<template><transitionname='fade'><h1v-if='bol'>但使龍城飛將在,不教胡馬度陰山!</h1></transition><button@click='bol=!bol'>切換</button></template><scriptsetup>import{ref}from'vue'constbol=ref(true)</script><stylelang='scss'scoped>.fade-enter-from{opacity:0;color:red;}.fade-enter-active{transition:all1sease;}.fade-enter-to{opacity:1;color:black;}.fade-leave-from{opacity:1;color:black;}.fade-leave-active{transition:all1.5sease;}.fade-leave-to{opacity:0;color:blue;}</style>

24、在Vue3中,v-on的.native修飾符已被移除。

25、同一節(jié)點(diǎn)上使用 v-for 和 v-if ,在Vue2中不推薦這么用,且v-for優(yōu)先級(jí)更高。在Vue3中,這種寫法是允許的,但 v-if 的優(yōu)秀級(jí)更高。

26、在Vue2中,靜態(tài)屬性和動(dòng)態(tài)屬性同時(shí)使用時(shí),不確定最終哪個(gè)起作用。在Vue3中,這是可以確定的,當(dāng)動(dòng)態(tài)屬性使用 :title 方式綁定時(shí),誰(shuí)在前面誰(shuí)起作用;當(dāng)動(dòng)態(tài)屬性使用 v-bind='object'方式綁定時(shí),誰(shuí)在后面誰(shuí)起作用。

<template><!-- 這種寫法,同時(shí)綁定靜態(tài)和動(dòng)態(tài)屬性時(shí),誰(shuí)在前面誰(shuí)生效! --><divid='red':id='("blue")'>不負(fù)當(dāng)下</div><div:title='("hello")'title='world'>不畏未來(lái)</div><hr><!-- 這種寫法,同時(shí)綁定靜態(tài)和動(dòng)態(tài)屬性時(shí),誰(shuí)在后面誰(shuí)生效! --><divid='red'v-bind='{id:"blue"}'>不負(fù)當(dāng)下</div><divv-bind='{title:"hello"}'title='world'>不畏未來(lái)</div></template>

27、當(dāng)使用watch選項(xiàng)偵聽數(shù)組時(shí),只有在數(shù)組被替換時(shí)才會(huì)觸發(fā)回調(diào)。換句話說(shuō),在數(shù)組被改變時(shí)偵聽回調(diào)將不再被觸發(fā)。要想在數(shù)組被改變時(shí)觸發(fā)偵聽回調(diào),必須指定deep選項(xiàng)。

<template><divv-for='t in list'v-text='t.task'></div><button@click.once='addTask'>添加任務(wù)</button></template><scriptsetup>import{reactive,watch}from'vue'constlist=reactive([{id:1,task:'讀書',value:'book'},{id:2,task:'跑步',value:'running'}])constaddTask=()=>{list.push({id:3,task:'學(xué)習(xí)',value:'study'})}// 當(dāng)無(wú)法監(jiān)聽一個(gè)引用類型的變量時(shí)// 添加第三個(gè)選項(xiàng)參數(shù) { deep:true }? watch(list,()=>{console.log('list changed',list)},{deep:true})</script>

28、在Vue2中接收 props時(shí),如果 prop的默認(rèn)值是工廠函數(shù),那么在這個(gè)工廠函數(shù)里是有 this的。在Vue3中,生成 prop 默認(rèn)值的工廠函數(shù)不再能訪問(wèn)this了。

<template><!-- v-for循環(huán)一個(gè)對(duì)象 --><divv-for='(v,k,i) in info'><spanv-text='i'></span>-<spanv-text='k'></span>-<spanv-text='v'></span></div><!-- v-for循環(huán)一個(gè)數(shù)組 --><divv-for='n in list'v-text='n'></div></template><scriptsetup>import{defineProps,inject}from'vue'// 為該 prop 指定一個(gè) default 默認(rèn)值時(shí),// 如果是對(duì)象或數(shù)組類型,默認(rèn)值必須從一個(gè)工廠函數(shù)返回。constprops=defineProps({info:{type:Object,default(){// 在Vue3中,這里是沒有this的,但可以訪問(wèn)injectconsole.log('this',this)// nullreturninject('info',{name:'張三',age:10})}},list:{type:Array,default(){returninject('list',[1,2,3,4])}}})</script>

29、Vue3中,新增了 <teleport>組件,這相當(dāng)于 ReactDOM.createPortal(),它的作用是把指定的元素或組件渲染到任意父級(jí)作用域的其它DOM節(jié)點(diǎn)上。上面第 16個(gè)知識(shí)點(diǎn)中,用到了 <teleport> 加載 animate.css 樣式表,這算是一種應(yīng)用場(chǎng)景。

除此之外,<teleport>還常用于封裝 Modal 彈框組件,示例代碼如下:

# Modal.vue<template><!-- 當(dāng)Modal彈框顯示時(shí),將其插入到<body>標(biāo)簽中去 --><teleportto='body'><divclass='layer'v-if='visibled'@click.self='cancel'><divclass='modal'><header></header><main><slot></slot></main><footer></footer></div></div></teleport></template><scriptsetup>import{defineProps,defineEmits,toRefs}from'vue'constprops=defineProps({visibled:{type:Boolean,default:false}})constemit=defineEmits(['cancel'])constcancel=()=>emit('cancel')</script><stylelang="scss">.layer{position:fixed;bottom:0;top:0;right:0;left:0;background-color:rgba(0,0,0,0.7);.modal{width:520px;position:absolute;top:100px;left:50%;margin-left:-260px;box-sizing:border-box;padding:20px;border-radius:3px;background-color:white;}}</style>

在業(yè)務(wù)頁(yè)面組件中使用自定義封裝的 Modal 彈框組件:

<template><Modal:visibled='show'@cancel='show=!show'><div>彈框主體內(nèi)容</div></Modal><button@click='show=!show'>打開彈框</button></template><scriptsetup>import{ref,watch}from'vue'importModalfrom'./components/Modal.vue'constshow=ref(false)</script>

30、在Vue3中,移除了 model 選項(xiàng),移除了 v-bind 指令的 .sync 修飾符。在Vue2中,v-model 等價(jià)于 :value + @input ;在Vue3中,v-model 等價(jià)于 :modelValue + @update:modelValue 。在Vue3中,同一個(gè)組件上可以同時(shí)使用多個(gè) v-model。在Vue3中,還可以自定義 v-model 的修飾符。

封裝帶有多個(gè) v-model的自定義組件:

# GoodFilter.vue<template><span>請(qǐng)選擇商家(多選):</span><spanv-for='s in shopArr'><inputtype='checkbox':value='s.value':checked='shop.includes(s.value)'@change='shopChange'/><spanv-text='s.label'></span></span><br><span>請(qǐng)選擇價(jià)格(單選):</span><spanv-for='p in priceArr'><inputtype='radio':value='p.value':checked='p.value===price'@change='priceChange'/><spanv-text='p.label'></span></span></template><scriptsetup>import{reactive,defineProps,defineEmits,toRefs}from'vue'constprops=defineProps({shop:{type:Array,default:[]},// 接收v-model:shop的自定義修飾符shopModifiers:{default:()=>({})},price:{type:Number,default:500},// 接收v-model:price的自定義修飾符priceModifiers:{default:()=>({})}})const{shop,price}=toRefs(props)// 接收v-model的自定義事件constemit=defineEmits(['update:shop','update:price'])constshopArr=reactive([{id:1,label:'華為',value:'huawei'},{id:2,label:'小米',value:'mi'},{id:3,label:'魅族',value:'meizu'},{id:4,label:'三星',value:'samsung'}])constpriceArr=reactive([{id:1,label:'1000以下',value:500},{id:2,label:'1000~2000',value:1500},{id:3,label:'2000~3000',value:2500},{id:4,label:'3000以上',value:3500}])// 多選框constshopChange=ev=>{const{checked,value}=ev.target// 使用v-model:shop的自定義修飾符const{sort}=props.shopModifiersletnewShop=(checked?[...shop.value,value]:shop.value.filter(e=>e!==value))if(sort)newShop=newShop.sort()emit('update:shop',newShop)}// 單選框constpriceChange=ev=>{emit('update:price',Number(ev.target.value))}</script><stylelang='scss'scoped>.nav{&>span{display:inline-block;padding:5px15px;}&>span.on{color:red;}}</style>

使用帶有多個(gè) v-model 的自定義組件:

<template><GoodFilterv-model:shop.sort='shop'v-model:price='price'/></template><scriptsetup>import{ref,reactive,watch}from'vue'importGoodFilterfrom'./components/GoodFilter.vue'constshop=ref([])constprice=ref(500)watch([shop,price],()=>{console.log('changed',shop.value,price.value)})</script>

六、寫在最后

后續(xù)繼續(xù)分享?Vue3響應(yīng)式原理、Vite構(gòu)建工具、Pinia(2)、ElementPlus、Vant(3)?等的使用。Vue3全家桶值得深入學(xué)習(xí)與關(guān)注,為Vue開發(fā)者帶來(lái)全新的開發(fā)體驗(yàn)。


轉(zhuǎn)發(fā)備份摘自于 https://zhuanlan.zhihu.com/p/482851017? 感謝知識(shí)分享

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容