分享一个 v-tooltip 指令

5,623 阅读2分钟

前言


我有个习惯,就是喜欢逛一些 ui 框架的网站,看看不同的框架都有哪些不一样的组件。发现 vuetifyjs 中有个指令 v-tooltip 好奇他的实现,能不能搬迁到我常用的 element-plus 中使用。于是克隆完源码,剥开实现的指令,核心代码不是很多,但是需要和组件相关联,比如l-tooltip 的值是 content,vuetify 值是 text。不一致,观察到 vuetify 的 Btn 组件为了 Tooltip 是有预留传参接口的。直接使用还是不可行。于是想到 el-tooltip 中有个虚拟用法,传递想要绑定的元素能实现相邻元素的提示效果。最后实现想要的效果。

疑问


从代码书写的出发点来看,嵌套的写法实现 tooltip 是比较的臃肿。

el-tooltip.png

这是一个 button,当 button 过多,代码数量也是很多的。像 element 原生支持的 title="提示"(如下图) 其实很简洁。vue 中指令形式和这个很相似。


下列用 p 元素讲解 title 属性。

image.png

<p class="border inline-block cursor-pointer" title="Lorem ipsum">Lorem ipsum, dolor sit amet consectetur adipisicing elit.</p>

title 书写方式和 v-directive 相似。如果用指令形式完成效果,确实符合原生 html 规范且不失优雅。


在 vuetifyjs 中有我想要的效果

作为前端裁缝,拷贝过来,取其精华,弃之糟粕(贝爷说一切万物掐头去尾都能用),实现目的。

我们在 directives 目录下新建一个文件 名为 useDirectiveComponent.js

import { h, mergeProps, render, resolveComponent } from 'vue'
import { isObject } from '@/utils'

export function useDirectiveComponent(component, props) {
  const concreteComponent = typeof component === 'string' ? resolveComponent(component) : component
  const hook = mountComponent(concreteComponent, props)
  return {
    mounted: hook,
    updated: hook,
    unmounted(el) {
      render(null, el)
    },
  }
}

function mountComponent(component, props) {
  return function (el, binding) {
    const _props = typeof props === 'function' ? props(el, binding) : props
    const text = binding.value?.text ?? binding.value ?? _props?.text
    const value = isObject(binding.value) ? binding.value : {}

    const children = () => text ?? el.textContent

    const node = h(component, mergeProps(_props, value), children)

    render(node, el)
  }
}

可以看出这是一段通用的指令挂载工具方法。

import { ElTooltip } from 'element-plus'
import { useDirectiveComponent } from '@/directives/useDirectiveComponent'

const Tooltip = useDirectiveComponent(ElTooltip, (el, binding) => {
  return {
    content: typeof binding.value === 'boolean' ? undefined : binding.value,
    placement: binding.arg,
    'virtual-triggering': true,
    'virtual-ref': el,
  }
})

export default (app) => {
  app.directive('tooltip', Tooltip)
}

我们注册一下就完成类似的元素原始属性 title 的效果。

效果演示

下面我们看下代码和实际效果

image.png

示例代码

    <el-row>
      <el-col class="mt-40" :span="12" :offset="6">
        <!-- v-tooltip -->
        <el-button v-tooltip:top="'primary 提示信息'" type="primary">Primary</el-button>
      </el-col>

      <el-col class="mt-10" :span="12" :offset="6">
        <!-- title -->
        <p class="border inline-block cursor-pointer" title="Lorem ipsum">Lorem ipsum, dolor sit amet consectetur adipisicing elit.</p>
      </el-col>

      <el-col class="mt-10" :offset="6">
        <!-- 原始 tooltip 写法 -->
        <el-tooltip effect="dark" content="Bottom Left prompts info" placement="bottom-start">
          <el-button type="primary">bottom-start</el-button>
        </el-tooltip>
      </el-col>
    </el-row>

我不生产代码,我只是代码的搬运工。