当前位置:首页 >知识 >Vue3自定义指令实践:使用h函数渲染自定义组件到指令中 那么仅展示 loading 图

Vue3自定义指令实践:使用h函数渲染自定义组件到指令中 那么仅展示 loading 图

2024-06-18 06:39:38 [百科] 来源:避面尹邢网

Vue3自定义指令实践:使用h函数渲染自定义组件到指令中

作者:萌萌哒草头将军 开发 前端 为了可以应对更多场景,自指令组件我们期望可以配置加载中的定义定义提示信息,不配置使用默认值,实践使用数渲如果是染自 false ,那么仅展示 loading 图。令中

🚀 关键接口介绍

最近想体验下自定义指令功能,自指令组件看了看文档和 vue2 差异不大,定义定义语法如下:

const myDirective = { // 在绑定元素的实践使用数渲 attribute 前 // 或事件监听器应用前调用created(el, binding, vnode, prevVnode) {  // 下面会介绍各个参数的细节 }, // 在元素被插入到 DOM 前调用beforeMount(el, binding, vnode, prevVnode) { },// 在绑定元素的父组件// 及他自己的所有子节点都挂载完成后调用mounted(el, binding, vnode, prevVnode) { },// 绑定元素的父组件更新前调用beforeUpdate(el, binding, vnode, prevVnode) { },// 在绑定元素的父组件// 及他自己的所有子节点都更新后调用updated(el, binding, vnode, prevVnode) { },// 绑定元素的父组件卸载前调用beforeUnmount(el, binding, vnode, prevVnode) { },// 绑定元素的父组件卸载后调用unmounted(el, binding, vnode, prevVnode) { }}

起初,最大的染自痛点是需要手动创建 dom ,然后插入 el 中。令中

Vue3自定义指令实践:使用h函数渲染自定义组件到指令中 那么仅展示 loading 图

const loadingDom = document.createElement('div',自指令组件 { calss: 'loading'})el.append(loadingDom)

这样好难受啊,我不想写原生 dom ,定义定义能不能写个组件渲染到指令里呢?

Vue3自定义指令实践:使用h函数渲染自定义组件到指令中 那么仅展示 loading 图

我想起了我之前看到的实践使用数渲几个 vue 接口,

Vue3自定义指令实践:使用h函数渲染自定义组件到指令中 那么仅展示 loading 图

  • h函数,染自也就是令中 vue 提供的创建 vNode 的函数
  • render函数:将 vNode 渲染到 真实 dom 里的函数

h函数用法如下:

// 完整参数签名function h(    type: string | Component,    props?: object | null,    children?: Children | Slot | Slots): VNode

例如:

import {  h } from 'vue'const vnode = h('div', {  class: 'container' }, [  h('h1', 'Hello, Vue 3'),  h('p', 'This is a paragraph')])

我们使用h函数创建了一个 VNode,它表示一个包含 div、h1、p 的 DOM 结构。其中,div 的 class 属性为 container

🚀 自定义 loading 组件

然而,当我使用 props 为组件传递值时,发现是徒劳的。

import Loading from './components/Loading.vue';cconst option = {     msg: '一大波僵尸来袭',    loading: true}const vnode = h(    Loading,    {  id: 'loading', ...option})

仅仅如下图一样,我以为是给组件的 props,实际上是 dom 的 props。

图片图片

此时我们的组件只能通过 $attrs 来获取这些 props 了,如下:

<template>  <div class="loading">    <div></div>    <span v-if="$attrs.msg !== false">{ {  $attrs.msg }}</span>  </div>  </template>

接着我们给组件实现 loading 动画,当然你也可以直接使用组件库的 loading 组件。

我的实现如下:

<style>  @keyframes identifier {     100% {       -webkit-transform: rotate(360deg);      transform: rotate(360deg);    }  }  .loading {     height: 100px;    width: 100%;  }  .loading div {     width: 50px;    height: 50px;    border-radius: 50%;    border: 2px solid green;    margin: 25px auto;    border-top: none;    border-left: none;    animation: identifier 1s infinite linear;  }</style>

🚀 自定义指令

接下来我们封装自定义指令。

我们的思路是:

  • mounted 阶段,如果是 true,那么渲染组件,否则什么都不做。
  • update 阶段,如果 true 则重新渲染组件,如果 false 则渲染 vnode

为了可以应对更多场景,我们期望可以配置加载中的提示信息,不配置使用默认值,如果是 false ,那么仅展示 loading 图。所以参数类型如下:

interface Props  { loading: boolean, msg?: string | false}v-loading: boolean | Props

由于可能是布尔值,也可能是对象,我们需要初始化配置参数

function formatOption (value: boolean | Props) {   const loading = typeof value === 'boolean'      ? value       : value.loading  const option = typeof value !== 'boolean'    ? Object.assign(defaultOption, value)    : {       loading,      ...defaultOption    }  return {  loading, option }}

接着再 mounted 阶段获取格式化后的 loading 和 option,如果为 true 则直接渲染组件。

const vLoading: Directive<HTMLElement, boolean | Props> = {   mounted(el, binding) {     const {  loading, option } = formatOption(binding.value)    loading && renderLoading(el, option)  }}function renderLoading (el: HTMLElement, option: Props) {   const vnode = h(    Loading,    {  id: 'loading', ...option}  )  el.removeChild(el.children[0])  render(vnode, el)}

如果进入 update 阶段,则根据情况选择渲染 laoding 组件还是 vnode。

const vLoading: Directive<HTMLElement, boolean | Props> = {   mounted(el, binding) {     const {  loading, option } = formatOption(binding.value)    loading && renderLoading(el, option)  },  updated(el: HTMLElement, binding, vnode) {     const {  loading, option } = formatOption(binding.value)    if (loading) {       renderLoading(el, option)    } else {       renderVNode(el, vnode)    }  },}function renderLoading (el: HTMLElement, option: Props) {   const vnode = h(    Loading,    {  id: 'loading', ...option}  )  el.removeChild(el.children[0])  render(vnode, el)}function renderVNode (el: HTMLElement, vnode: VNode) {   el.querySelector('#loading')?.remove()  render(vnode, el)}

我们验证下功能:

  • 默认功能
const loading = ref(true)setTimeout(() => loading.value = false, 2000)</script><template>  <div style="width: 300px" v-loading=laoding>    <h1>加载成功</h1>  </div></template>

图片图片

  • 自定义文本
<template>  <div style="width: 300px" v-loading="{  loading, msg: '一大波僵尸来袭' }">    <h1>加载成功</h1>  </div></template>

图片图片

  • 不展示文本
<template>  <div style="width: 300px" v-loading="{  loading, msg: false }">    <h1>加载成功</h1>  </div></template>

图片图片

🎉 最后

今天的分享就到这了,如果有问题,可以评论区告诉我,我会及时更正。

以下是完整的代码。

<template>  <div class="loading">    <div></div>    <span v-if="$attrs.msg !== false">{ {  $attrs.msg }}</span>  </div>  </template>  <script lang="ts">export default {   }</script>  <style>  @keyframes identifier {     100% {       -webkit-transform: rotate(360deg);      transform: rotate(360deg);    }  }  .loading {     height: 100px;    width: 100%;  }  .loading div {     width: 50px;    height: 50px;    border-radius: 50%;    border: 2px solid green;    margin: 25px auto;    border-top: none;    border-left: none;    animation: identifier 1s infinite linear;  }</style>
<script setup lang="ts">import {  Directive, VNode, h, ref, render  } from 'vue';import Loading from './components/Loading.vue';const defaultOption: { msg?: string | false} = {   msg: '努力加载中'}interface Props  { loading: boolean, msg?: string | false}function formatOption (value: boolean | Props) {   const loading = typeof value === 'boolean'      ? value       : value.loading  const option = typeof value !== 'boolean'    ? Object.assign(defaultOption, value)    : {       loading,      ...defaultOption    }  return {  loading, option }}function renderLoading (el: HTMLElement, option: Props) {   const vnode = h(    Loading,    {  id: 'loading', ...option}  )  el.removeChild(el.children[0])  render(vnode, el)}function renderVNode (el: HTMLElement, vnode: VNode) {   el.querySelector('#loading')?.remove()  render(vnode, el)}const vLoading: Directive<HTMLElement, boolean | Props> = {   mounted(el, binding) {     const {  loading, option } = formatOption(binding.value)    loading && renderLoading(el, option)  },  updated(el: HTMLElement, binding, vnode) {     const {  loading, option } = formatOption(binding.value)    if (loading) {       renderLoading(el, option)    } else {       renderVNode(el, vnode)    }  },}const loading = ref(true)setTimeout(() => loading.value = false, 2000)</script><template>  <div style="width: 300px" v-loading="{  loading, msg: '一大波僵尸来袭' }">    <h1>加载成功</h1>  </div></template>
责任编辑:武晓燕 来源: 萌萌哒草头将军 场景vue3自定义

(责任编辑:探索)

    推荐文章
    热点阅读