Skip to content

Vue3优雅的复用模板代码

Posted on:2023年5月24日 at 11:26

在 Angular 中有一个很好用的功能,即在不封装组件的情况下复用模板代码:

eg:

<ng-template let-title="title" #helloTemplate>
  <h1>hello, {{title}}</h1>
</ng-template>

<ng-container
  *ngTemplateOutlet="helloTemplate;context: {title: 'cxk'}"
></ng-container>
<ng-container
  *ngTemplateOutlet="helloTemplate;context: {title: 'world'}"
></ng-container>

我们通过ng-templateng-containerngTemplateOutlet即可实现模板复用,非常的好用!!

那么在 Vue3 中如何实现如此神奇的功能呢?

我们将其封装成 hooks /src/hooks/useReusableTemplate.ts

import type { DefineComponent, Slot } from "vue";
import { defineComponent, shallowRef } from "vue";

export type DefineTemplateComponent<
  Bindings extends object,
  Slots extends Record<string, Slot | undefined>
> = DefineComponent<{}> & {
  new (): { $slots: { default(_: Bindings & { $slots: Slots }): any } };
};

export type ReuseTemplateComponent<
  Bindings extends object,
  Slots extends Record<string, Slot | undefined>
> = DefineComponent<Bindings> & {
  new (): { $slots: Slots };
};

export function useReusableTemplate<
  Bindings extends object,
  Slots extends Record<string, Slot | undefined> = Record<
    string,
    Slot | undefined
  >
>() {
  const render = shallowRef<Slot | undefined>();

  const define = defineComponent({
    setup(_, { slots }) {
      return () => {
        render.value = slots.default;
      };
    },
  }) as DefineTemplateComponent<Bindings, Slots>;

  const reuse = defineComponent({
    inheritAttrs: false,
    setup(_, { attrs, slots }) {
      return () => {
        if (!render.value && process.env.NODE_ENV !== "production")
          throw new Error("Failed to find the definition of reusable template");
        return render.value?.({ ...attrs, $slots: slots });
      };
    },
  }) as ReuseTemplateComponent<Bindings, Slots>;

  return [define, reuse] as const;
}

Usage

<script setup lang="ts">
import { useReusableTemplate } from "@/hooks/useReusableTemplate";
import { ref } from "vue";

const [DefineTemplate, ReuseTemplate] = useReusableTemplate<{
  title: string;
}>();

const count = ref(0);

const handleClick = () => {
  count.value += 1;
};
</script>

<template>
  <DefineTemplate v-slot="{ title }">
    <el-button @click="handleClick">{{ title }} - {{ count }}</el-button>
  </DefineTemplate>

  <ReuseTemplate title="金" />
  <ReuseTemplate title="木" />
  <ReuseTemplate title="水" />
  <ReuseTemplate title="火" />
  <ReuseTemplate title="土" />
</template>

某些在不必封装组件的情况,就可实现复用模板代码了,太好用了!

这个创意来自antfuvueuse中已经有这个 hooks 了: createReusableTemplate