likes
comments
collection
share

vue3中用30行代码实现一个cmd k全局搜索框

作者站长头像
站长
· 阅读数 12

概述

本文主要介绍在vue3中用h函数动态生成组件,通过函数调用的方式去挂载dom,控制组件的显示和隐藏,例如各类ui框架中,$message等方法的调用实现。

下面通过一个全局搜索框的例子来介绍实现方法

实现

需要准备3个文件 :一个搜索框ui组件serachBar.vue,(构造函数类searchBarC.ts,一个方法调用处index.vue 30行代码内

serachBar.vue

首先先完成一个常规的vue组件,用于视图显示,并补充部分样式,这个组件大家根据各自实际需要去写就行了,你可以是一个对话框,一个确认框,也可以像该案例中一个搜索框。

我这里写了一个非常简陋的搜索框,大家不嫌弃可以简单套用一下。 这里加了一个渐出动画

<template>
  <div class="search-bar">
    <input
      v-model="state.input"
      placeholder="请输入内容"
      @keyup.enter.native="handle"
    />
    <button @click="handle">搜索</button>
  </div>
</template>

<script setup lang="ts">
import { onMounted, reactive } from "vue";
const state = reactive({
  input: "",
});
function handle() {
  console.log("handle");
}
</script>

<style lang="less" scoped>
.search-bar {
  position: absolute;
  top: 30%;
  left: 50%;
  transform: translate(-50%, 0);
  z-index: 1000;
  width: 60%;
  height: 60px;
  line-height: 60px;
  text-align: center;
  background: rgb(78 78 78 / 5%);
  backdrop-filter: blur(8px);
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 10px 5px 5px #8d80ad;
  animation: searchShow 1s;
}

@keyframes searchShow {
  0% {
    opacity: 0;
    transform: translate(-50%, 50px);
  }
  100% {
    opacity: 1;
    transform: translate(-50%, 0);
  }
}
</style>

searchBarC.ts

我们用一个构造类去封装组件的生成和挂载的行为,主要讲一下这个show方法。 整个过程分为3步:

  1. 通过h函数,把我们提供的.vue组件转化为一个VNode虚拟节点。(因为Vue 其实就是先渲染 虚拟 dom -->然后 转换成真实 dom,而平常通过template的写法其实就是个语法糖,最终内部都要转为VNode的。)vue3中用30行代码实现一个cmd k全局搜索框
  2. 调用render函数,将刚才的虚拟节点挂载到真实的dom上,也就是生成真实dom结构的过程。(createApp(App).mount("#app") 这种写法总熟悉了吧,其实是一样的道理)
  3. 到这里时,我们只是创建了一个dom结构,但是没有添加浏览器的页面结构内,所以最后一步只需要把appendChild把他塞进去就好了
import { h, render } from "vue";
import search from "./serachBar.vue";
class SerachBarCreator {
  container: HTMLDivElement;
  constructor() {
    this.container = document.createElement("div");
  }
  show() {
    let vnode = h(search);
    render(vnode, this.container);
    document.body.appendChild(this.container);
  }
  hide() {
    document.body.removeChild(this.container);
  }
}
let searchBar = new SerachBarCreator();
export default searchBar;


到这里,其实我们已经写完了,剩下的就是调用它了。

index.vue

调用就更简单了,因为实例化的过程在内部就做了,导出直接是实例,所以只要执行实例的方法就好了。


import searchBar from "./searchBarC";
function handle() {
  searchBar.show();
}
function handle2() {
  searchBar.hide();
}

另外需要实现command+k组合键唤起,只要添加一个键盘输入的监听事件就好了

onMounted(() => {
  // keydown 事件支持多个按键同时按下
  window.addEventListener("keydown", (e) => {
    if (e.metaKey && e.key === "k") {
      searchBar.show();
    }
    // esc键
    if (e.key === "Escape") {
      searchBar.hide();
    }
  });
});

到此为止就全部完成了,快去按下按键试试吧

效果展示

vue3中用30行代码实现一个cmd k全局搜索框

思考

在这个过程中其实有一个细节点,当时我在实现的时候想到,如果多次调用show方法,会不会导致多个组件被渲染到页面上,那么就需要实现一个单例模型去限制这种情况。 但是在实际操作中发现 多次执行document.body.appendChild(this.container);这一步并不会多次添加dom到页面当中,仍然只会保持一个,这是为什么呢?

后来我去查了MDN-appendChild,发现里面是这样写到:

  • Node.appendChild()  方法将一个节点附加到指定父节点的子节点列表的末尾处。如果将被插入的节点已经存在于当前文档的文档树中,那么 appendChild() 只会将它从原先的位置移动到新的位置(不需要事先移除要移动的节点)。

  • 这意味着,一个节点不可能同时出现在文档的不同位置。所以,如果某个节点已经拥有父节点,在被传递给此方法后,它首先会被移除,再被插入到新的位置。若要保留已在文档中的节点,可以先使用 Node.cloneNode() 方法来为它创建一个副本,再将副本附加到目标父节点下。请注意,用 cloneNode 制作的副本不会自动保持同步。

因为创建这个节点只在构造函数初始化的过程中执行了一次, this.container = document.createElement("div"); 后续我们都是在操作这同一个节点,但是如果我们多次执行了document.createElement,那就会创建出多个不同的节点,此时appendChild 就会添加多个不同的节点进去。

以上~