Vue3實(shí)現(xiàn)高階組件HOC的示例詳解
前言
高階組件HOC
在React社區(qū)是非常常見的概念,但是在Vue社區(qū)中卻是很少人使用。主要原因有兩個(gè):1、Vue中一般都是使用SFC,實(shí)現(xiàn)HOC比較困難。2、HOC能夠?qū)崿F(xiàn)的東西,在Vue2時(shí)代mixins
能夠?qū)崿F(xiàn),在Vue3時(shí)代Composition API
能夠?qū)崿F(xiàn)。如果你不知道HOC,那么你平時(shí)絕對(duì)沒有場景需要他。但是如果你知道HOC,那么在一些特殊的場景使用他就可以很優(yōu)雅的解決一些問題。
什么是高階組件HOC
HOC使用場景就是加強(qiáng)原組件
。
HOC實(shí)際就是一個(gè)函數(shù),這個(gè)函數(shù)接收的參數(shù)就是一個(gè)組件,并且返回一個(gè)組件,返回的就是加強(qiáng)后組件。如下圖:
在Composition API
出現(xiàn)之前HOC還有一個(gè)常見的使用場景就是提取公共邏輯,但是有了Composition API
后這種場景就無需使用HOC了。
高階組件HOC使用場景
很多同學(xué)覺得有了Composition API
后,直接無腦使用他就完了,無需費(fèi)時(shí)費(fèi)力的去搞什么HOC。那如果是下面這個(gè)場景呢?
有一天產(chǎn)品找到你,說要給我們的系統(tǒng)增加會(huì)員功能,需要讓系統(tǒng)中的幾十個(gè)功能塊增加會(huì)員可見功能。如果不是會(huì)員這幾十個(gè)功能塊都顯示成引導(dǎo)用戶開通會(huì)員的UI,并且這些功能塊涉及到幾十個(gè)組件,分布在系統(tǒng)的各個(gè)頁面中。
如果不知道HOC的同學(xué)一般都會(huì)這樣做,將會(huì)員相關(guān)的功能抽取成一個(gè)名為useVip.ts
的hooks。代碼如下:
export function useVip() { function getShowVipContent() { // 一些業(yè)務(wù)邏輯判斷是否是VIP return false; } return { showVipContent: getShowVipContent(), }; }
然后再去每個(gè)具體的業(yè)務(wù)模塊中去使用showVipContent
變量判斷,v-if="showVipContent"
顯示原模塊,v-else
顯示引導(dǎo)開通會(huì)員UI。代碼如下:
<template> <Block1 v-if="showVipContent" :name="name1" @changeName="(value) => (name1 = value)" /> <OpenVipTip v-else /> </template> <script setup lang="ts"> import { ref } from "vue"; import Block1 from "./block1.vue"; import OpenVipTip from "./open-vip-tip.vue"; import { useVip } from "./useVip"; const { showVipContent } = useVip(); const name1 = ref("block1"); </script>
我們系統(tǒng)中有幾十個(gè)這樣的組件,那么我們就需要這樣去改幾十次。非常麻煩,如果有些模塊是其他同事寫的代碼還很容易改錯(cuò)?。。?/p>
而且現(xiàn)在流行搞SVIP,也就是光開通VIP還不夠,需要再開通一個(gè)SVIP。當(dāng)你后續(xù)接到SVIP需求時(shí),你又需要去改這幾十個(gè)模塊。v-if="SVIP"
顯示某些內(nèi)容,v-else-if="VIP"
顯示提示開通SVIP,v-else
顯示提示開通VIP。
上面的這一場景使用hooks去實(shí)現(xiàn),雖然能夠完成,但是因?yàn)槿肭至诉@幾十個(gè)模塊的業(yè)務(wù)邏輯。所以容易出錯(cuò),也改起來比較麻煩,代碼也不優(yōu)雅。
那么有沒有一種更好的解決方案,讓我們可以不入侵這幾十個(gè)模塊的業(yè)務(wù)邏輯的實(shí)現(xiàn)方式呢?
答案是:高階組件HOC
。
HOC的一個(gè)用途就是對(duì)組件進(jìn)行增強(qiáng),并且不會(huì)入侵原有組件的業(yè)務(wù)邏輯,在這里就是使用HOC判斷會(huì)員相關(guān)的邏輯。如果是會(huì)員那么就渲染原本的模塊組件,否則就渲染引導(dǎo)開通VIP的UI
實(shí)現(xiàn)一個(gè)簡單的HOC
首先我們要明白Vue的組件經(jīng)過編譯后就是一個(gè)對(duì)象,對(duì)象中的props
屬性對(duì)應(yīng)的就是我們寫的defineProps
。對(duì)象中的setup方法,對(duì)應(yīng)的就是我們熟知的<script setup>
語法糖。
比如我使用console.log(Block1)
將上面的import Block1 from "./block1.vue";
給打印出來,如下圖:
這個(gè)就是我們引入的Vue組件對(duì)象。
還有一個(gè)冷知識(shí),大家可能不知道。如果在setup方法中返回一個(gè)函數(shù),那么在Vue內(nèi)部就會(huì)認(rèn)為這個(gè)函數(shù)就是實(shí)際的render函數(shù),并且在setup方法中我們天然的就可以訪問定義的變量。
利用這一點(diǎn)我們就可以在Vue3中實(shí)現(xiàn)一個(gè)簡單的高階組件HOC,代碼如下:
import { h } from "vue"; import OpenVipTip from "./open-vip-tip.vue"; export default function WithVip(BaseComponent: any) { return { setup() { const showVipContent = getShowVipContent(); function getShowVipContent() { // 一些業(yè)務(wù)邏輯判斷是否是VIP return true; } return () => { return showVipContent ? h(BaseComponent) : h(OpenVipTip); }; }, }; }
在上面的代碼中我們將會(huì)員相關(guān)的邏輯全部放在了WithVip
函數(shù)中,這個(gè)函數(shù)接收一個(gè)參數(shù)BaseComponent
,他是一個(gè)Vue組件對(duì)象。
在setup
方法中我們r(jià)eturn了一個(gè)箭頭函數(shù),他會(huì)被當(dāng)作render函數(shù)處理。
如果showVipContent
為true,就表明當(dāng)前用戶開通了VIP,就使用h
函數(shù)渲染傳入的組件。
否則就渲染OpenVipTip
組件,他是引導(dǎo)用戶開通VIP的組件。
此時(shí)我們的父組件就應(yīng)該是下面這樣的:
<template> <EnhancedBlock1 /> </template> <script setup lang="ts"> import Block1 from "./block1.vue"; import WithVip from "./with-vip.tsx"; const EnhancedBlock1 = WithVip(Block1); </script>
這個(gè)代碼相比前面的hooks的實(shí)現(xiàn)就簡單很多了,只需要使用高階組件WithVip
對(duì)原來的Block1
組件包一層,然后將原本使用Block1
的地方改為使用EnhancedBlock1
。對(duì)原本的代碼基本沒有入侵。
上面的例子只是一個(gè)簡單的demo,他是不滿足我們實(shí)際的業(yè)務(wù)場景。比如子組件有props
、emit
、插槽
。還有我們?cè)诟附M件中可能會(huì)直接調(diào)用子組件expose暴露的方法。
因?yàn)槲覀兪褂昧薍OC對(duì)原本的組件進(jìn)行了一層封裝,那么上面這些場景HOC都是不支持的,我們需要添加一些額外的代碼去支持。
高階組件HOC實(shí)現(xiàn)props和emit
在Vue中屬性分為兩種,一種是使用props
和emit
聲明接收的屬性。第二種是未聲明的屬性attrs
,比如class、style、id等。
在setup函數(shù)中props是作為第一個(gè)參數(shù)返回,attrs
是第二個(gè)參數(shù)中返回。
所以為了能夠支持props和emit,我們的高階組件WithVip
將會(huì)變成下面這樣:
import { SetupContext, h } from "vue"; import OpenVipTip from "./open-vip-tip.vue"; export default function WithVip(BaseComponent: any) { return { props: BaseComponent.props, // 新增代碼 setup(props, { attrs, slots, expose }: SetupContext) { // 新增代碼 const showVipContent = getShowVipContent(); function getShowVipContent() { // 一些業(yè)務(wù)邏輯判斷是否是VIP return true; } return () => { return showVipContent ? h(BaseComponent, { ...props, // 新增代碼 ...attrs, // 新增代碼 }) : h(OpenVipTip); }; }, }; }
在setup
方法中接收的第一個(gè)參數(shù)就是props
,沒有在props中定義的屬性就會(huì)出現(xiàn)在attrs
對(duì)象中。
所以我們調(diào)用h函數(shù)時(shí)分別將props
和attrs
透傳給子組件。
同時(shí)我們還需要一個(gè)地方去定義props,props的值就是直接讀取子組件對(duì)象中的BaseComponent.props
。所以我們給高階組件聲明一個(gè)props屬性:props: BaseComponent.props,
。
這樣props就會(huì)被透傳給子組件了。
看到這里有的小伙伴可能會(huì)問,那emit觸發(fā)事件沒有看見你處理呢?
答案是:我們無需去處理,因?yàn)楦附M件上面的@changeName="(value) => (name1 = value)"
經(jīng)過編譯后就會(huì)變成屬性::onChangeName="(value) => (name1 = value)"
。而這個(gè)屬性由于我們沒有在props中聲明,所以他會(huì)作為attrs
直接透傳給子組件。
高階組件實(shí)現(xiàn)插槽
我們的正常子組件一般還有插槽,比如下面這樣:
<template> <div class="divider"> <h1>{{ name }}</h1> <button @click="handleClick">change name</button> <slot /> 這里是block1的一些業(yè)務(wù)代碼 <slot name="footer" /> </div> </template> <script setup lang="ts"> const emit = defineEmits<{ changeName: [name: string]; }>(); const props = defineProps<{ name: string; }>(); const handleClick = () => { emit("changeName", `hello ${props.name}`); }; defineExpose({ handleClick, }); </script>
在上面的例子中,子組件有個(gè)默認(rèn)插槽和name為footer
的插槽。此時(shí)我們來看看高階組件中如何處理插槽呢?
直接看代碼:
import { SetupContext, h } from "vue"; import OpenVipTip from "./open-vip-tip.vue"; export default function WithVip(BaseComponent: any) { return { props: BaseComponent.props, setup(props, { attrs, slots, expose }: SetupContext) { const showVipContent = getShowVipContent(); function getShowVipContent() { // 一些業(yè)務(wù)邏輯判斷是否是VIP return true; } return () => { return showVipContent ? h( BaseComponent, { ...props, ...attrs, }, slots // 新增代碼 ) : h(OpenVipTip); }; }, }; }
插槽的本質(zhì)就是一個(gè)對(duì)象里面擁有多個(gè)方法,這些方法的名稱就是每個(gè)具名插槽,每個(gè)方法的參數(shù)就是插槽傳遞的變量。這里我們只需要執(zhí)行h
函數(shù)時(shí)將slots
對(duì)象傳給h函數(shù),就能實(shí)現(xiàn)插槽的透傳(如果你看不懂這句話,那就等歐陽下篇插槽的文章寫好后再來看這段話你就懂了)。
我們?cè)诳刂婆_(tái)中來看看傳入的slots
插槽對(duì)象,如下圖:
從上面可以看到插槽對(duì)象中有兩個(gè)方法,分別是default
和footer
,對(duì)應(yīng)的就是默認(rèn)插槽和footer插槽。
大家熟知h函數(shù)接收的第三個(gè)參數(shù)是children數(shù)組,也就是有哪些子元素。但是他其實(shí)還支持直接傳入slots
對(duì)象,下面這個(gè)是他的一種定義:
export function h<P>( type: Component<P>, props?: (RawProps & P) | null, children?: RawChildren | RawSlots, ): VNode export type RawSlots = { [name: string]: unknown // ...省略 }
所以我們可以直接把slots對(duì)象直接丟給h函數(shù),就可以實(shí)現(xiàn)插槽的透傳。
父組件調(diào)用子組件的方法
有的場景中我們需要在父組件中直接調(diào)用子組件的方法,按照以前的場景,我們只需要在子組件中expose暴露出去方法,然后在父組件中使用ref訪問到子組件,這樣就可以調(diào)用了。
但是使用了HOC后,中間層多了一個(gè)高階組件,所以我們不能直接訪問到子組件expose的方法。
怎么做呢?答案很簡單,直接上代碼:
import { SetupContext, h, ref } from "vue"; import OpenVipTip from "./open-vip-tip.vue"; export default function WithVip(BaseComponent: any) { return { props: BaseComponent.props, setup(props, { attrs, slots, expose }: SetupContext) { const showVipContent = getShowVipContent(); function getShowVipContent() { // 一些業(yè)務(wù)邏輯判斷是否是VIP return true; } // 新增代碼start const innerRef = ref(); expose( new Proxy( {}, { get(_target, key) { return innerRef.value?.[key]; }, has(_target, key) { return innerRef.value?.[key]; }, } ) ); // 新增代碼end return () => { return showVipContent ? h( BaseComponent, { ...props, ...attrs, ref: innerRef, // 新增代碼 }, slots ) : h(OpenVipTip); }; }, }; }
在高階組件中使用ref
訪問到子組件賦值給innerRef
變量。然后expose一個(gè)Proxy
的對(duì)象,在get攔截中讓其直接去執(zhí)行子組件中的對(duì)應(yīng)的方法。
比如在父組件中使用block1Ref.value.handleClick()
去調(diào)用handleClick
方法,由于使用了HOC,所以這里讀取的handleClick
方法其實(shí)是讀取的是HOC中expose暴露的方法。所以就會(huì)走到Proxy
的get攔截中,從而可以訪問到真正子組件中expose暴露的handleClick
方法。
那么上面的Proxy為什么要使用has
攔截呢?
答案是在Vue源碼中父組件在執(zhí)行子組件中暴露的方法之前會(huì)執(zhí)行這樣一個(gè)判斷:
if (key in target) { return target[key]; }
很明顯我們這里的Proxy
代理的原始對(duì)象里面什么都沒有,執(zhí)行key in target
肯定就是false了。所以我們可以使用has
去攔截key in target
,意思是只要訪問的方法或者屬性是子組件中expose
暴露的就返回true。
至此,我們已經(jīng)在HOC中覆蓋了Vue中的所有場景。但是有的同學(xué)覺得h
函數(shù)寫著比較麻煩,不好維護(hù),我們還可以將上面的高階組件改為tsx的寫法,with-vip.tsx
文件代碼如下:
import { SetupContext, ref } from "vue"; import OpenVipTip from "./open-vip-tip.vue"; export default function WithVip(BaseComponent: any) { return { props: BaseComponent.props, setup(props, { attrs, slots, expose }: SetupContext) { const showVipContent = getShowVipContent(); function getShowVipContent() { // 一些業(yè)務(wù)邏輯判斷是否是VIP return true; } const innerRef = ref(); expose( new Proxy( {}, { get(_target, key) { return innerRef.value?.[key]; }, has(_target, key) { return innerRef.value?.[key]; }, } ) ); return () => { return showVipContent ? ( <BaseComponent {...props} {...attrs} ref={innerRef}> {slots} </BaseComponent> ) : ( <OpenVipTip /> ); }; }, }; }
一般情況下h函數(shù)能夠?qū)崿F(xiàn)的,使用jsx
或者tsx
都能實(shí)現(xiàn)(除非你需要操作虛擬DOM)。
注意上面的代碼是使用ref={innerRef}
,而不是我們熟悉的ref="innerRef"
,這里很容易搞錯(cuò)??!
compose函數(shù)
此時(shí)你可能有個(gè)新需求,需要給某些模塊顯示不同的折扣信息,這些模塊可能會(huì)和上一個(gè)會(huì)員需求的模塊有重疊。此時(shí)就涉及到多個(gè)高階組件之間的組合情況。
同樣我們使用HOC去實(shí)現(xiàn),新增一個(gè)WithDiscount
高階組件,代碼如下:
import { SetupContext, onMounted, ref } from "vue"; export default function WithDiscount(BaseComponent: any, item: string) { return { props: BaseComponent.props, setup(props, { attrs, slots, expose }: SetupContext) { const discountInfo = ref(""); onMounted(async () => { const res = await getDiscountInfo(item); discountInfo.value = res; }); function getDiscountInfo(item: any): Promise<string> { // 根據(jù)傳入的item獲取折扣信息 return new Promise((resolve) => { setTimeout(() => { resolve("我是折扣信息1"); }, 1000); }); } const innerRef = ref(); expose( new Proxy( {}, { get(_target, key) { return innerRef.value?.[key]; }, has(_target, key) { return innerRef.value?.[key]; }, } ) ); return () => { return ( <div class="with-discount"> <BaseComponent {...props} {...attrs} ref={innerRef}> {slots} </BaseComponent> {discountInfo.value ? ( <div class="discount-info">{discountInfo.value}</div> ) : null} </div> ); }; }, }; }
那么我們的父組件如果需要同時(shí)用VIP功能和折扣信息功能需要怎么辦呢?代碼如下:
const EnhancedBlock1 = WithVip(WithDiscount(Block1, "item1"));
如果不是VIP,那么這個(gè)模塊的折扣信息也不需要顯示了。
因?yàn)楦唠A組件接收一個(gè)組件,然后返回一個(gè)加強(qiáng)的組件。利用這個(gè)特性,我們可以使用上面的這種代碼將其組合起來。
但是上面這種寫法大家覺得是不是看著很難受,一層套一層。如果這里同時(shí)使用5個(gè)高階組件,這里就會(huì)套5層了,那這個(gè)代碼的維護(hù)難度就是地獄難度了。
所以這個(gè)時(shí)候就需要compose
函數(shù)了,這個(gè)是React社區(qū)中常見的概念。它的核心思想是將多個(gè)函數(shù)從右到左依次組合起來執(zhí)行,前一個(gè)函數(shù)的輸出作為下一個(gè)函數(shù)的輸入。
我們這里有多個(gè)HOC(也就是有多個(gè)函數(shù)),我們期望執(zhí)行完第一個(gè)HOC得到一個(gè)加強(qiáng)的組件,然后以這個(gè)加強(qiáng)的組件為參數(shù)去執(zhí)行第二個(gè)HOC,最后得到由多個(gè)HOC加強(qiáng)的組件。
compose
函數(shù)就剛好符合我們的需求,這個(gè)是使用compose
函數(shù)后的代碼,如下:
const EnhancedBlock1 = compose(WithVip, WithDiscount("item1"))(Block1);
這樣就舒服多了,所有的高階組件都放在第一個(gè)括弧里面,并且由右向左去依次執(zhí)行每個(gè)高階組件HOC。如果某個(gè)高階組件HOC需要除了組件之外的額外參數(shù),像WithDiscount
這樣處理就可以了。
很明顯,我們的WithDiscount
高階組件的代碼需要修改才能滿足compose
函數(shù)的需求,這個(gè)是修改后的代碼:
import { SetupContext, onMounted, ref } from "vue"; export default function WithDiscount(item: string) { return (BaseComponent: any) => { return { props: BaseComponent.props, setup(props, { attrs, slots, expose }: SetupContext) { const discountInfo = ref(""); onMounted(async () => { const res = await getDiscountInfo(item); discountInfo.value = res; }); function getDiscountInfo(item: any): Promise<string> { // 根據(jù)傳入的item獲取折扣信息 return new Promise((resolve) => { setTimeout(() => { resolve("我是折扣信息1"); }, 1000); }); } const innerRef = ref(); expose( new Proxy( {}, { get(_target, key) { return innerRef.value?.[key]; }, has(_target, key) { return innerRef.value?.[key]; }, } ) ); return () => { return ( <div class="with-discount"> <BaseComponent {...props} {...attrs} ref={innerRef}> {slots} </BaseComponent> {discountInfo.value ? ( <div class="discount-info">{discountInfo.value}</div> ) : null} </div> ); }; }, }; }; }
注意看,WithDiscount
此時(shí)只接收一個(gè)參數(shù)item
,不再接收BaseComponent
組件對(duì)象了,然后直接return出去一個(gè)回調(diào)函數(shù)。
準(zhǔn)確的來說此時(shí)的WithDiscount
函數(shù)已經(jīng)不是高階組件HOC了,他return出去的回調(diào)函數(shù)才是真正的高階組件HOC
。在回調(diào)函數(shù)中去接收BaseComponent
組件對(duì)象,然后返回一個(gè)增強(qiáng)后的Vue組件對(duì)象。
至于參數(shù)item
,因?yàn)殚]包所以在里層的回調(diào)函數(shù)中還是能夠訪問的。這里比較繞,可能需要多理解一下。
前面的理解完了后,我們可以再上一點(diǎn)強(qiáng)度了。來看看compose
函數(shù)是如何實(shí)現(xiàn)的,代碼如下:
function compose(...funcs) { return funcs.reduce((acc, cur) => (...args) => acc(cur(...args))); }
這個(gè)函數(shù)雖然只有一行代碼,但是乍一看,怎么看怎么懵逼,歐陽也是?。∥覀冞€是結(jié)合demo來看:
const EnhancedBlock1 = compose(WithA, WithB, WithC, WithD)(View);
假如我們這里有WithA
、WithB
、 WithC
、 WithD
四個(gè)高階組件,都是用于增強(qiáng)組件View
。
compose中使用的是...funcs
將調(diào)用compose
函數(shù)接收到的四個(gè)高階組件都存到了funcs
數(shù)組中。
然后使用reduce去遍歷這些高階組件,注意看執(zhí)行reduce
時(shí)沒有傳入第二個(gè)參數(shù)。
所以第一次執(zhí)行reduce時(shí),acc
的值為WithA
,cur
的值為WithB
。返回結(jié)果也是一個(gè)回調(diào)函數(shù),將這兩個(gè)值填充進(jìn)去就是(...args) => WithA(WithB(...args))
,我們將第一次的執(zhí)行結(jié)果命名為r1
。
我們知道reduce會(huì)將上一次的執(zhí)行結(jié)果賦值為acc,所以第二次執(zhí)行reduce時(shí),acc
的值為r1
,cur
的值為WithC
。返回結(jié)果也是一個(gè)回調(diào)函數(shù),同樣將這兩個(gè)值填充進(jìn)行就是(...args) => r1(WithC(...args))
。同樣我們將第二次的執(zhí)行結(jié)果命名為r2
。
第三次執(zhí)行reduce時(shí),此時(shí)的acc
的值為r2
,cur
的值為WithD
。返回結(jié)果也是一個(gè)回調(diào)函數(shù),同樣將這兩個(gè)值填充進(jìn)行就是(...args) => r2(WithD(...args))
。同樣我們將第三次的執(zhí)行結(jié)果命名為r3
,由于已經(jīng)將數(shù)組遍歷完了,最終reduce的返回值就是r3
,他是一個(gè)回調(diào)函數(shù)。
由于compose(WithA, WithB, WithC, WithD)
的執(zhí)行結(jié)果為r3
,那么compose(WithA, WithB, WithC, WithD)(View)
就等價(jià)于r3(View)
。
前面我們知道r3
是一個(gè)回調(diào)函數(shù):(...args) => r2(WithD(...args))
,這個(gè)回調(diào)函數(shù)接收的參數(shù)args
,就是需要增強(qiáng)的基礎(chǔ)組件View
。所以執(zhí)行這個(gè)回調(diào)函數(shù)就是先執(zhí)行WithD
對(duì)組件進(jìn)行增強(qiáng),然后將增強(qiáng)后的組件作為參數(shù)去執(zhí)行r2
。
同樣r2
也是一個(gè)回調(diào)函數(shù):(...args) => r1(WithC(...args))
,接收上一次WithD
增強(qiáng)后的組件為參數(shù)執(zhí)行WithC
對(duì)組件再次進(jìn)行增強(qiáng),然后將增強(qiáng)后的組件作為參數(shù)去執(zhí)行r1
。
同樣r1
也是一個(gè)回調(diào)函數(shù):(...args) => WithA(WithB(...args))
,將WithC
增強(qiáng)后的組件丟給WithB
去執(zhí)行,得到增強(qiáng)的組件再丟給WithA
去執(zhí)行,最終就拿到了最后增強(qiáng)的組件。
執(zhí)行順序就是從右向左
去依次執(zhí)行高階組件對(duì)基礎(chǔ)組件進(jìn)行增強(qiáng)。
至此,關(guān)于compose
函數(shù)已經(jīng)講完了,這里對(duì)于Vue的同學(xué)可能比較難理解,建議多看兩遍。
總結(jié)
這篇文章我們講了在Vue3中如何實(shí)現(xiàn)一個(gè)高階組件HOC,但是里面涉及到了很多源碼知識(shí),所以這是一篇運(yùn)用源碼的實(shí)戰(zhàn)文章。如果你理解了文章中涉及到的知識(shí),那么就會(huì)覺得Vue中實(shí)現(xiàn)HOC還是很簡單的,反之就像是在看天書。
還有最重要的一點(diǎn)就是Composition API
已經(jīng)能夠解決絕大部分的問題,只有少部分的場景才需要使用高階組件HOC,切勿強(qiáng)行使用HOC
,那樣可能會(huì)有炫技的嫌疑。如果是防御性編程,那么就當(dāng)我沒說。
最后就是我們實(shí)現(xiàn)的每個(gè)高階組件HOC都有很多重復(fù)的代碼,而且實(shí)現(xiàn)起來很麻煩,心智負(fù)擔(dān)也很高。那么我們是不是可以抽取一個(gè)createHOC
函數(shù)去批量生成高階組件呢?這個(gè)就留給各位自己去思考了。
還有一個(gè)問題,我們這種實(shí)現(xiàn)的高階組件叫做正向?qū)傩源?/code>,弊端是每代理一層就會(huì)增加一層組件的嵌套。那么有沒有方法可以解決嵌套的問題呢?
答案是反向繼承
,但是這種也有弊端如果業(yè)務(wù)是setup中返回的render函數(shù),那么就沒法重寫了render函數(shù)了。
到此這篇關(guān)于Vue3實(shí)現(xiàn)高階組件HOC的示例詳解的文章就介紹到這了,更多相關(guān)Vue3高階組件HOC內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一文了解Vue 3 的 generate 是這樣生成 render&n
本文介紹generate階段是如何根據(jù)javascript AST抽象語法樹生成render函數(shù)字符串的,本文中使用的vue版本為3.4.19,感興趣的朋友跟隨小編一起看看吧2024-06-06vuedraggable實(shí)現(xiàn)簡單拖拽功能
這篇文章主要為大家詳細(xì)介紹了vuedraggable實(shí)現(xiàn)拖拽功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04Vue3組件中g(shù)etCurrentInstance()獲取App實(shí)例,但是返回null的解決方案
這篇文章主要介紹了Vue3組件中g(shù)etCurrentInstance()獲取App實(shí)例,但是返回null的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04vue3限制table表格選項(xiàng)個(gè)數(shù)的解決方法
這篇文章主要為大家詳細(xì)介紹了vue3限制table表格選項(xiàng)個(gè)數(shù)的解決方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04django+vue項(xiàng)目搭建實(shí)現(xiàn)前后端通信
本文主要介紹了django+vue項(xiàng)目搭建實(shí)現(xiàn)前后端通信,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02buildAdmin開源項(xiàng)目引入四種圖標(biāo)方式詳解
這篇文章主要為大家介紹了buildAdmin開源項(xiàng)目引入四種圖標(biāo)方式詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02vue 中的動(dòng)態(tài)傳參和query傳參操作
這篇文章主要介紹了vue 中的動(dòng)態(tài)傳參和query傳參操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-11-11