自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Vue3自定義指令實(shí)踐:使用h函數(shù)渲染自定義組件到指令中

開發(fā) 前端
為了可以應(yīng)對(duì)更多場(chǎng)景,我們期望可以配置加載中的提示信息,不配置使用默認(rèn)值,如果是 false ,那么僅展示 loading 圖。

?? 關(guān)鍵接口介紹

最近想體驗(yàn)下自定義指令功能,看了看文檔和 vue2 差異不大,語(yǔ)法如下:

const myDirective = {
// 在綁定元素的 attribute 前 
// 或事件監(jiān)聽器應(yīng)用前調(diào)用
created(el, binding, vnode, prevVnode) 
{ // 下面會(huì)介紹各個(gè)參數(shù)的細(xì)節(jié) }, 
// 在元素被插入到 DOM 前調(diào)用
beforeMount(el, binding, vnode, prevVnode) {},
// 在綁定元素的父組件
// 及他自己的所有子節(jié)點(diǎn)都掛載完成后調(diào)用
mounted(el, binding, vnode, prevVnode) {},
// 綁定元素的父組件更新前調(diào)用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在綁定元素的父組件
// 及他自己的所有子節(jié)點(diǎn)都更新后調(diào)用
updated(el, binding, vnode, prevVnode) {},
// 綁定元素的父組件卸載前調(diào)用
beforeUnmount(el, binding, vnode, prevVnode) {},
// 綁定元素的父組件卸載后調(diào)用
unmounted(el, binding, vnode, prevVnode) {}
}

起初,最大的痛點(diǎn)是需要手動(dòng)創(chuàng)建 dom ,然后插入 el 中。

const loadingDom = document.createElement('div', {calss: 'loading'})
el.append(loadingDom)

這樣好難受啊,我不想寫原生 dom ,能不能寫個(gè)組件渲染到指令里呢?

我想起了我之前看到的幾個(gè) vue 接口,

  • h函數(shù),也就是 vue 提供的創(chuàng)建 vNode 的函數(shù)
  • render函數(shù):將 vNode 渲染到 真實(shí) dom 里的函數(shù)

h函數(shù)用法如下:

// 完整參數(shù)簽名
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函數(shù)創(chuàng)建了一個(gè) VNode,它表示一個(gè)包含 div、h1、p 的 DOM 結(jié)構(gòu)。其中,div 的 class 屬性為 container

?? 自定義 loading 組件

然而,當(dāng)我使用 props 為組件傳遞值時(shí),發(fā)現(xiàn)是徒勞的。

import Loading from './components/Loading.vue';

cconst option = {
    msg: '一大波僵尸來(lái)襲',
    loading: true
}

const vnode = h(
    Loading,
    { id: 'loading', ...option}
)

僅僅如下圖一樣,我以為是給組件的 props,實(shí)際上是 dom 的 props。

圖片圖片

此時(shí)我們的組件只能通過(guò) $attrs 來(lái)獲取這些 props 了,如下:

<template>

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

接著我們給組件實(shí)現(xiàn) loading 動(dòng)畫,當(dāng)然你也可以直接使用組件庫(kù)的 loading 組件。

我的實(shí)現(xiàn)如下:

<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>

?? 自定義指令

接下來(lái)我們封裝自定義指令。

我們的思路是:

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

為了可以應(yīng)對(duì)更多場(chǎng)景,我們期望可以配置加載中的提示信息,不配置使用默認(rèn)值,如果是 false ,那么僅展示 loading 圖。所以參數(shù)類型如下:

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

v-loading: boolean | Props

由于可能是布爾值,也可能是對(duì)象,我們需要初始化配置參數(shù)

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)
}

如果進(jìn)入 update 階段,則根據(jù)情況選擇渲染 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)
}

我們驗(yàn)證下功能:

  • 默認(rèn)功能
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: '一大波僵尸來(lái)襲' }">
    <h1>加載成功</h1>
  </div>
</template>

圖片圖片

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

圖片圖片

?? 最后

今天的分享就到這了,如果有問題,可以評(píng)論區(qū)告訴我,我會(huì)及時(shí)更正。

以下是完整的代碼。

<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: '一大波僵尸來(lái)襲' }">
    <h1>加載成功</h1>
  </div>
</template>

責(zé)任編輯:武曉燕 來(lái)源: 萌萌噠草頭將軍
相關(guān)推薦

2021-11-30 08:19:43

Vue3 插件Vue應(yīng)用

2022-07-26 01:06:18

Vue3自定義指令

2022-02-22 13:14:30

Vue自定義指令注冊(cè)

2021-07-05 15:35:47

Vue前端代碼

2023-07-21 19:16:59

OpenAIChatGPT

2020-12-28 10:10:04

Vue自定義指令前端

2024-09-26 14:16:07

2022-08-01 11:41:00

Vue插件

2022-04-24 15:17:56

鴻蒙操作系統(tǒng)

2010-10-25 16:05:07

oracle自定義函數(shù)

2010-05-11 13:16:21

Unix awk

2015-02-12 15:33:43

微信SDK

2023-12-21 09:00:21

函數(shù)React 組件useEffect

2017-05-19 10:03:31

AndroidBaseAdapter實(shí)踐

2021-12-24 15:46:23

鴻蒙HarmonyOS應(yīng)用

2021-11-23 15:06:42

Kubernetes 運(yùn)維開源

2023-08-11 09:16:14

2023-12-01 09:02:57

Vue3WangEditor

2010-09-14 16:47:23

SQL自定義函數(shù)

2015-02-12 15:38:26

微信SDK
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)