在做后台项目时候,使用声明式组件比较多,就是写一个.vue文件,在里面写 templatescriptstyle哪里需要,就在哪里导入。

而对于前台项目而言,我们期望可以直接通过方法的形式调用,利用函数式组件,在封装时除了要写.vue,还要多一个手动渲染和卸载的步骤。我们可以通过 h 函数可以生成一个vnode,该vnode利用render函数渲染和卸载。

<template>
  <!-- 声明式组件 -->
  <el-row class="mb-4">
    <el-button type="primary"  @click="open">Primary</el-button>
    <el-button type="success">Success</el-button>
  </el-row>
</template>

<script lang="ts" setup>
// 函数式组件
import { ElMessage } from 'element-plus'
// 通常是在某个交互完成时触发
const open = () => {
  ElMessage('this is a message.')
}
</script>

1.1. h 函数

Vue中,提供了一个h()函数用于创建 vnodes。创建虚拟节点时会多种不同情况,比如传入标签名和属性,就会创建一个标签的虚拟节点,传入组件名和属性,就会创建一个组件的虚拟节点。

h()接收三个参数(要渲染的dom,attrs 对象,子元素)

h()有一个更准确的名称是 createVnode(),考虑到多次使用,一个简短的名字会更省力。

import { h } from 'vue'

// 使用 h 创建普通标签  
const vnode1 = h('div', { class: 'bar', innerHTML: 'hello' })
// vnode1 等同于 <div class="bar">hello</div>
  
// 使用 h 创建组件
const vnode2 =  h(myComponent, {
		//组件的属性
    title: '测试'
})
// vnode2 等同于 <myComponent title="测试"/>

1.2. render 函数

render()接收标签或者组件的 vnode,将其渲染成为真实 DOM,并挂载到一个指定的父节点上。

import { h, render } from 'vue'

render(vnode2, document.body)
render(null, document.body)     // 当第1个参数为null时,相当于从父节点上移除此组件。

1.3. confirm组件

以实现 comfirm 组件为例,具体实现逻辑如下:

  1. 创建一个 confirm 组件
  2. 创建一个 comfirm.js 模块,该模块返回一个 promise
  3. 同时利用 h()生成 confirm.vuevode
  4. 最后利用 render函数,渲染 vnodebody

1.3.1. 构建 confirm.vue 组件

<script setup>
  import { ref, onMounted } from 'vue'
  // 因为将来 confirm 组件是以方法调用的形式展示,所以我们需要手动导入需要使用到的其他通用组件
  import mButton from '@/libs/button/index.vue'

  const props = defineProps({
    // 标题
    title: {
      type: String
    },
    // 描述
    content: {
      type: String,
      required: true
    },
    // 取消按钮文本
    cancelText: {
      type: String,
      default: '取消'
    },
    // 确定按钮文本
    confirmText: {
      type: String,
      default: '确定'
    },
    // 取消按钮事件
    cancelHandler: {
      type: Function
    },
    // 确定按钮事件
    confirmHandler: {
      type: Function
    },
    // 关闭 confirm 的回调
    close: {
      type: Function
    }
  })

  // 控制显示处理
  const isVisible = ref(false)
  /**
   * confirm 展示
   */
  const show = () => {
    isVisible.value = true
  }

  /**
   * 处理动画 (render 函数的渲染,会直接进行)
   */
  onMounted(() => {
    show()
  })

  /**
   * 取消事件
   */
  const onCancelClick = () => {
    if (props.cancelHandler) {
      props.cancelHandler()
    }
    close()
  }

  /**
   * 确定事件
   */
  const onConfirmClick = () => {
    if (props.confirmHandler) {
      props.confirmHandler()
    }
    close()
  }

  // 关闭动画处理时间
  const duration = '0.5s'
  /**
   * 关闭事件,保留动画执行时长
   */
  const close = () => {
    isVisible.value = false
    // 延迟一段时间进行关闭
    setTimeout(() => {
      if (props.close) {
        props.close()
      }
    }, parseInt(duration.replace('0.', '').replace('s', '')) * 100)
  }
</script>


<template>
  <!-- 基于 tailwindcss 创建对应样式 -->
  <div>
    <!-- 蒙版 -->
    <transition name="fade">
      <div
        v-if="isVisible"
        @click="close"
        class="w-screen h-screen bg-zinc-900/80 z-40 fixed left-0 top-0"
        ></div>
    </transition>

    <!-- 内容 -->
    <transition name="up">
      <div
        v-if="isVisible"
        class="w-[80%] fixed top-1/3 left-[50%] translate-x-[-50%] z-50 px-2 py-1.5 rounded-sm border dark:border-zinc-600 cursor-pointer bg-white dark:bg-zinc-800 xl:w-[35%]"
        @click="close"
        >
        <!-- 标题 -->
        <div class="text-lg font-bold text-zinc-800 dark:text-zinc-200 mb-2">{{ title }}</div>
        <!-- 文本 -->
        <div class="text-base tex-zinc-800 dark:text-zinc-200 mb-2">{{ content }}</div>
        <!-- 按钮 -->
        <div class="flex justify-end">
          <m-button type="info" class="mr-2" @click="onCancelClick">{{ cancelText }}</m-button>
          <m-button type="primary" @click="onConfirmClick">{{ confirmText }}</m-button>
        </div>
      </div>
    </transition>
  </div>
</template>

<style lang="scss" scoped>
  .fade-enter-active,
  .fade-leave-active {
    transition: all v-bind(duration);
  }

  .fade-enter-from,
  .fade-leave-to {
    opacity: 0;
  }

  .up-enter-active,
  .up-leave-active {
    transition: all v-bind(duration);
  }

  .up-enter-from,
  .up-leave-to {
    opacity: 0;
    transform: translate3d(-50%, -100%, 0);
  }
</style>

1.3.2. 创建 confirm.js 模块

import { h, render } from 'vue'
import confirmComponent from './confirm.vue'

/**
 * @param {*} title 标题
 * @param {*} content 内容
 * @param {*} cancelText 取消文本
 * @param {*} confirmText 确认文本
 */
export const confirmBox = (title, content, cancelText, confirmText) => {
  return new Promise((resolve, reject) => {
    // 不传入标题,只传入内容时
    if (title && !content) {
      content = title
      title = ''
    }

    // 取消按钮事件
    const cancelHandler = () => {
      reject(new Error('取消按钮点击'))
    }

    // 确定按钮事件
    const confirmHandler = () => {
      resolve()
    }

    // 关闭弹层事件
    const close = () => {
      render(null, document.body)
    }
    
    // 1. 生成 vnode
    const vnode = h(confirmComponent, {
      title,
      content,
      cancelText,
      confirmText,
      cancelHandler,
      confirmHandler,
      close
    })

    // 2. render 渲染
    render(vnode, document.body)
  })
}

1.3.3. 触发 comfirm 组件

import  { confirmBox } from './confirm.js'

const onDeleteAllClick = () => {
  confirmBox('要删除所有历史记录吗?').then(() => {
    // 点击确定后执行事件
    ...
  })
  .catch(()=>{
    // 点击取消后执行事件
    ...
  })
}

学子资料:点此下载