Skip to content

Vue3 服务式(函数式)组件

Posted on:2022年7月7日 at 18:55

之前在vue2中实现过函数式调用组件,那么在vue3中该如何实现呢?

让我们在vue3中来实现一个函数式调用的组件吧

1. 在/src/components下创建Toast文件夹

Toast文件夹下创建toast.vue
<script setup lang="ts">
import { ref, unref } from "vue";

const _show = ref(false);
const _message = ref("hello");

const open = (message: string, delay = 1000) => {
  if (_show.value) return;
  _message.value = message;
  _show.value = true;
  setTimeout(close, unref(delay));
};

const close = () => {
  _show.value = false;
};

export interface ToastExposed {
  open: typeof open;
  close: typeof close;
}

defineExpose<ToastExposed>({ open, close });
</script>

<template>
  <transition name="toast">
    <div v-show="_show" class="toast">{{ _message }}</div>
  </transition>
</template>

<style scoped>
.toast {
  position: fixed;
  z-index: 100;
  bottom: 20%;
  left: 50%;
  padding: 6px 20px;
  transform: translateX(-50%);
  color: #fff;
  background-color: rgba(37, 38, 45, 0.9);
  border-radius: 6px;
  user-select: none;
}

.toast-enter-active {
  animation: fade 0.2s;
}

.toast-leave-active {
  animation: fade 0.2s reverse;
}

@keyframes fade {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}
</style>
Toast文件夹下创建index.ts
import { render, createVNode, VNode } from "vue";
import Toast from "./toast.vue";
import type { ToastExposed } from "./toast.vue";

const createToast = () => {
  const _vm = createVNode(Toast);
  const container = document.createElement("div");
  render(_vm, container);
  document.body.appendChild(container);
  return _vm;
};

let _toast: VNode | null = null;
const useToast = () => {
  if (!_toast) _toast = createToast();

  const toast = (message: string) => {
    (<ToastExposed>_toast?.component?.exposed)?.open(message);
  };

  return toast;
};

export default useToast;

createVNode 函数以将vue组件转为vnode

render函数可以将vnode渲染到真实DOM

2. Usage

<script lang="ts" setup>
import useToast from "@/components/Toast";

const toast = useToast();
</script>

<template>
  <button @click="toast('hello world')">button</button>
</template>