欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue3實(shí)現(xiàn)高階組件HOC的示例詳解

 更新時(shí)間:2025年01月07日 09:26:06   作者:前端歐陽  
高階組件HOC在React社區(qū)是非常常見的概念,但是在Vue社區(qū)中卻是很少人使用,但其實(shí)在一些特殊的場景使用他就可以很優(yōu)雅的解決一些問題,下面就跟隨小編一起來了解下吧

前言

高階組件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中屬性分為兩種,一種是使用propsemit聲明接收的屬性。第二種是未聲明的屬性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í)分別將propsattrs透傳給子組件。

同時(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è)方法,分別是defaultfooter,對(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的值為r1cur的值為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的生命周期一起來看看

    Vue的生命周期一起來看看

    這篇文章主要為大家詳細(xì)介紹了Vue的生命周期,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-02-02
  • vue監(jiān)聽用戶輸入和點(diǎn)擊功能

    vue監(jiān)聽用戶輸入和點(diǎn)擊功能

    這篇文章主要為大家詳細(xì)介紹了vue監(jiān)聽用戶輸入和點(diǎn)擊功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-09-09
  • 一文了解Vue 3 的 generate 是這樣生成 render 函數(shù)的

    一文了解Vue 3 的 generate 是這樣生成 render&n

    本文介紹generate階段是如何根據(jù)javascript AST抽象語法樹生成render函數(shù)字符串的,本文中使用的vue版本為3.4.19,感興趣的朋友跟隨小編一起看看吧
    2024-06-06
  • vuedraggable實(shí)現(xiàn)簡單拖拽功能

    vuedraggable實(shí)現(xiàn)簡單拖拽功能

    這篇文章主要為大家詳細(xì)介紹了vuedraggable實(shí)現(xiàn)拖拽功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • Vue3組件中g(shù)etCurrentInstance()獲取App實(shí)例,但是返回null的解決方案

    Vue3組件中g(shù)etCurrentInstance()獲取App實(shí)例,但是返回null的解決方案

    這篇文章主要介紹了Vue3組件中g(shù)etCurrentInstance()獲取App實(shí)例,但是返回null的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • vue3限制table表格選項(xiàng)個(gè)數(shù)的解決方法

    vue3限制table表格選項(xiàng)個(gè)數(shù)的解決方法

    這篇文章主要為大家詳細(xì)介紹了vue3限制table表格選項(xiàng)個(gè)數(shù)的解決方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • vue+element表格導(dǎo)出為Excel文件

    vue+element表格導(dǎo)出為Excel文件

    這篇文章主要為大家詳細(xì)介紹了vue+element表格導(dǎo)出為Excel文件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-09-09
  • django+vue項(xiàng)目搭建實(shí)現(xiàn)前后端通信

    django+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-02
  • buildAdmin開源項(xiàng)目引入四種圖標(biāo)方式詳解

    buildAdmin開源項(xiàng)目引入四種圖標(biāo)方式詳解

    這篇文章主要為大家介紹了buildAdmin開源項(xiàng)目引入四種圖標(biāo)方式詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-02-02
  • vue 中的動(dòng)態(tài)傳參和query傳參操作

    vue 中的動(dòng)態(tài)傳參和query傳參操作

    這篇文章主要介紹了vue 中的動(dòng)態(tài)傳參和query傳參操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11

最新評(píng)論