【Vue】1535- Vue3 如何实现一个全局搜索框

2024-03-21 14:50

本文主要是介绍【Vue】1535- Vue3 如何实现一个全局搜索框,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

461f0a26fc7b3beb04b345d87829bd04.jpeg

Vue3 如何实现一个全局搜索框

前言:自从学习 vue 以来,就对 vue 官网全局的 command + K 调出全局关键词搜索这个功能心心念念。恰好最近项目也是需要实现一个全局搜索的功能,也正好可以正大光明的带薪学习这个功能的思路。网上的教程水平参差不齐,而恰好之前的项目中我有做过一个类似于全局弹出面包屑的功能,于是举一反三写出了一个我们项目需要的全局搜索框,特来分享一下自己的思路.

注意:本文不会马上教你如何编写代码,而是作为一个引路人,一步一步引导你去理解这个组件的设计思路。会以 “假如我是一个初学者,如果我在学习这个知识的时候,别人能这样告诉我,那么我也可以很快的去理解” 的角度去讲解 ,授人以鱼不如授人以渔。希望你在阅读本文的时候可以拓展思路,举一反三。

一. 文件准备

前期你需要准备三个文件,来完成这个全局搜索框

  1. SearchBar.ts 文件

  2. SearchBar.vue 文件

  3. useSearch.ts 文件

a0cfa1f617f7c4ac7d09d315a0fb7aa5.jpeg
image.png

二. 搜索框的样式

样式问题不是本文的重点,你可以花费五分钟在 SearchBar.vue 文件内速写一个非常简易的正方形 div 包裹着一个 input 标签即可快速进行下面的学习。

但是首先我们需要理清思路,这个组件是会出现在我们页面的最顶部的,所以它组件内部需要用到绝对布局。我们去 SearchBar.vue 去设置一个样式给最外层的 div,这里其它样式的写法使用的是 Uno CSS,没用过的小伙伴也不需要担心,它只是单纯的样式,和本文中心内容不牵扯。(CSS写成计算属性在这个场景也毫无特殊意义,只是单纯设计时考虑多了) ac029671ad7dc3294959253a6c5a6ca1.jpege95d1cb04b90fbf9c5328448b8fc1006.jpeg56519c2a0276f1b826aecf6f9d5d3405.jpeg

三. 渲染函数 hrender 函数(重点)

  1. 打开之前准备的 SearchBar.ts 文件,从 vue 里引入这两个函数,并且把在上一步写好的简陋版搜索框(SearchBar.vue)引入到这个文件内。32ec033e09180ccdf233e67e35bbb9d1.jpeg

  2. 看过我之前文章 Vue3实现一个 Toast[2] 的读者可能会比较熟悉一点点,但是在那一篇文章内由于我也是初次接触这两个函数,所以当时总结的也不是特别精确,所以重新捋清思路,这里再讲解一下。

  3. 首先我们从官网的介绍,先看一下这个函数的定义。727cd3a8055e72a55a5029dca072c766.jpeg
    可以看出,这个函数第一个参数是必填的,可以是一个 stringComponent,这篇文章重点讨论参数为 Component 的情况。重点是这个函数的返回值,是一个 VNode,这个你一定不陌生,Virtual Node ,看本篇文章的读者可能对虚拟 dom 的原理可能不是那么清楚,但是我相信你们一定知道它的基本机制。Vue 其实是先渲染 虚拟 dom -->然后 转换成真实 dom

  4. 先别急着写代码,我想你可能更清楚这样的写法,比如我们前面在 SearchBar.vue 文件内写的简单的弹出框。5c74a885e21a23f60cb69388e3676f94.jpeg 整个组件的样式都是在 Vue 提供的 <template> 组件内写的,但是你要知道,Vue 在底层还是通过调用 h() 来完成虚拟 dom 的构建。而 <template> 仅仅只是 Vue 为了让你用熟悉的原生 html 开发而为你提供的语法糖🍬而已。(嗯,你可以这样理解)

  5. 那么我们可以根据上面 h() 函数的介绍,它接收的第一参数可以是 Component ,那我们这个 SearchBar.vue 不就是组件吗?那如果我不想使用 <template> 去展示这个组件的话,我是否可以这样写呢?h(SearchBar.vue)。没错,是的,你就是可以这样写。别忘了 h 的返回值就是我们想拿到的 Vnode ,所以按照正确的写法是这样的。9702c9a306d0cc9046250af9994086cb.jpeg

三. 编写 SearchBarMaker 构造函数和 present 方法

  1. 让我们回到 SearchBar.ts 文件。2f894e2a836b4278d052f0c030b09869.jpeg

  2. 首先思考,这个搜索框一定有一个出现的函数,和一个消失的函数🤔,ok,起名字,一个 present,一个 dismiss
    bc5caaba7d751ec6550704f7e4423432.jpeg

  3. 接下来我需要创建出一个 VNode ,然后想办法处理成真实 dom。经过上面的学习,第一步马上就可以想到下面的写法。
    a65d97d8da1d3e1071504bc9d3f656cf.jpeg

  4. 下面这位更是重量级,render() 函数。虚拟 dom 有了,真实dom 该如何拿到呢?Vue 为我们提供了这样一个函数,这里我们需要重点去看这个函数的类型是值,是一个 RootRenderFunction 类型的。b94cfa48ca8c06b3c47c638fdba7edac.jpeg

  5. 这里我们转变一下思路,我们看一下 render 函数的第二个参数是 一个 container:HostElement ,然后让我们打开我们 main.ts 文件,我们跳进 mount的定义部分, 19c16fd2934909b32df284d8b2b063ff.jpeg22941254cbbc06139268963e424da24f.jpeg 发现神奇的地方了吗,我们虽然不知道 HostElement 的类型是什么,但是你知道你 mount 函数内填的参数是什么了吗?(忘掉的转头自觉复习官网哈。)
    没错,就是全局唯一的一个真实 dom,一个朴实无华的id叫 appdiv 元素。5dd218a8af5b6417a83bcbfbdf49e16e.jpeg 由于篇幅限制,在这里你可以先暂时简单的理解,render 函数会将你的虚拟 dom 包装成一个真实 dom 元素,但是你需要给它一个真实的 外壳dom 来告诉它将虚拟 dom 渲染到哪个位置。

  6. ok,拿到一个包装后的虚拟 dom ,接下来就是告诉浏览器在哪里渲染这个元素。这里我们需要思考🤔,既然是全局都可以弹出的,并且需要在所有组件之上弹出。eebc091990543475f64eab2aad09c778.jpeg 那么最简单的方法就是让它出现在 body的第一个元素,那么它一定会和我们网页所有的组件同级别(tips:通常我们所有的页面构成都会写在 body内 的一个 div 内。什么?你问我为什么?请打开你的 index.html 看一下,你是否忘记了我们的 App.vue 是挂在这个真实的,id为 app 的元素内的) fc2f70aa55d91e313d8121139d247dde.jpeg 那其实我们的操作的思路就是非常简单的,当我按下全局搜索按钮,那么你就在 <div id="app"> 的元素之前插入我的组件即可。8df4a62fc7afb683507a37453048f4d2.jpeg

  7. ok,到这里我们已经可以看到基本效果了,我们来测试一下。让我们在 App.vue 组件内随便写一个按钮,然后调用 SearchBarCreator 实例身上的 present 方法。(maker 感觉不是那么合理,之后我们将 SearchBarMaker 变更为 SeachBarCreator 的叫法,仅仅是名字变了而已,逻辑什么的根本没变哦。🍦) 92c1b19d8b051832456f2ad078ed92b6.jpeg 效果如下:

  8. 到这里 searchBar 已经可以呈现在页面上了,但是我们还不知道怎样让它消失,其实也非常简单,我们只需要在合适的时机移除这个 dom 元素即可。dfcf0207c13ff3417e7c52f6d759139e.jpeg 在这里我们需要知道一点,我们需要将 searchBar 提升到当前文件的全局,不能仅只在 open 中去 new 了。
    8c2d69c45bca0766153eca8446d5e213.jpeg ok,我们测试一下

四. 优化 SearchBarCreator 构造函数的代码逻辑

写到这里的时候,你可能发现了一个小问题,当我一直去按搜索按钮的时候,它会出现多个搜索框,但是我们希望的是它在全局只能出现一个搜索框。换个角度思考,也就是同一时间,这个被我们 new 出来的 SeachBar 实例只能出现一个。思考一下🤔,我加一个变量,isShowing 是否正在被展示 ,如果正在被展示的话,那么用户再次调用 present 的时候,我就去调用实例自身的 dismiss 方法让它消失,是否可行呢?
cf2bb1422b5fb8998471a87bd8269cfa.jpeg 测试一下:
OK,看来完美解决当前的问题了。

五. 编写全局唯一的调用实例

  1. 在上面的这种情况下,我们已经可以在 App.vue 文件内去 new 一个实例来调用这个搜索框了。但是我们加入现在需要在 XXX.vue 文件内调用这个搜索框呢?我难道还需要重新去引入,然后重新 new 吗?nonono,某位大佬说过,程序员都是很懒的,不可能写这种低级的重复代码的。那么该如何实现呢

  2. 打开我们之前准备的 useSearch.ts 文件,我们把之前在 App.vue 的全局生成的这个 SearchBar 实例转换思路,使它在全局的一个 ts 文件内生成一个,然后把这个实例自身的一些方法封装成函数,暴露给外部。那么我就可以在全局任意一个地方去调用这个实例身上的这两个方法。c9ea266f92d2181768d36582faae56f7.jpeg

  3. 让我们在 App.vue 去试一下。
    这是我们之前的 App.vue 文件的调用方法。fc47e9ba3ab31dbaf035a4a6b0696c3c.jpeg 我们改造一下它。de168c14d3067cf8c6362dab6017de0f.jpeg 我们再次测试一下功能有没有什么问题
    如此一来就方便很多了,我们可以在任意位置去调用这个“唯一的搜索框”

六. 添加全局的快捷键 Command + K

  1. 再此之前,我们需要理解一个概念,注意我们的 main.ts 文件,我们是把谁挂在了全局的那一个 id='app' 的真实 dom 下的?31953f0d9c667ac86a5a44c01f80156e.jpeg
    没错,就是前面我们提到的 App.vue 组件。

  2. 那么假如我在这个 App.vue 组件挂载的时候,给全局 window 对象身上添加一个键盘事件,是不是就可以了呢?怎么添加呢?其实非常非常简单,要用到见组合按键,我们就需要使用到 “keydown”,具体为什么不是 “keypress” ,读者可以自行查阅这两者的区别,不属于本文的主要探讨内容。7fab3c5876370c871fb3de2608bb12d3.jpeg

  3. 这时候,我们先来按一下 command 看看打印的内容是什么。这里重点的内容是该键盘事件身上的metaKey 属性。1b5b50d5f33e67661c3173a0c3b542e1.jpeg 在这里我们还可以推算出按下 “ctrl” 的事件为6705f29024fd265aa6ee246708feba87.jpeg

  4. keydown 事件支持多个按键同时按下。当我们同时按下 “command” 和 “K” 键,会发生什么呢?82759538d5d5ea31f7f923e1fe475c42.jpeg 但是我们发现好像并没有 K:true 这个属性呀,那我们怎么去判断呢?别着急接着往下看。

  5. 我们可以看到键盘事件 event 身上有个 key 属性,它的值恰好是字符串类型的 “k”78694f048ea239b6518cb460227557f0.jpeg

这里我直接公布写法,js 允许我们这样判断是否同时按下两个按键。4965acae61991760e50e35c75c2cea10.jpeg

  1. 我们测试一下,我们去吧 App.vue 文件内的这两个按钮给去掉 739dfa8850631f1240f1eae353013b99.jpeg 然后再打印一下我们按下 commandk 的时候。ca64d39a8b03c80bff7cfee0228a75c8.jpeg 测试一下:

七. 添加出现的动画

  1. 在上面我们可以看到,这样突然的出现好像有一丝丝的突兀。我希望这个搜索框在出现的时候,可以有那么一丝丝的平移效果,(类似于下面的效果)该如何做呢?🤔

  2. 我这里介绍一种较为简单的思路,我们在 App.vue 文件的 style 内预设一个 Css 动画,并起好名字。叫做 "searchInput" 30e0a531d12f471190e3fc931e065402.jpeg

  3. 然后回到我们 searBar.vue 的组件去,给我们这个组件最外层的起一个好听的名字,我这里就叫做 searchBarWrapper88852e9c47acb076d6fab5fa29dee488.jpeg

  4. 然后回到我们的 SearchBar.ts 文件内,也就是放我们 SeachBarCreator 构造函数的那个文件内。(tips:不是 useSearch.ts 哦) 我这里解释一下思路,在调用 render 函数后,这个组件其实已经渲染成为一个真实的 dom 元素,只不过我们还没给它指定渲染的位置。既然是真实的 dom ,那么我们就可以通过 document.getElementById这个方法(querySelector同理,一个意思)拿到这个SearchBar.vue组件 ,接下来我只需要在调用 document.body.insertBefore 方法前,给它添加上刚刚我们在 App.vue 里预设好的类名,searchInput ,就完美达成我们想要的效果了。f0236824f9113b4e364fe9adeeae5869.jpeg

  5. 注意:style ,这个点仅仅是类名选择器,不要忘记了基础知识。00f7cb1874ca7b1909cdbb7bdbaa779a.jpeg

  6. 测试一下效果:

八. 自动聚焦

在弹出框的 input 框实现自动聚焦相比于之前讲的就非常简单了,我在这里一笔带过了。只需要在 nextTick 中调用 input 本身的 focus 方法即可。
a3d50e250533d2a88e9316477016e4e5.jpeg

总结:

之所以不喜欢使用真代码去写文章而大量使用截图的原因是:我自己在搜索到自己想要的文章后,也会喜欢直接看有没有最后的成品代码,然后直接复制就拿过去用了,而往往忽略了自己动手去实现一遍才是真正理解了的过程。

所以我写代码的时候,尽量不写特别复杂的逻辑,而写一些很简单的几行代码去实现某一个功能。是因为我希望你们真正带入自己的思考,和一步步体会这个实现过程,从而举一反三。

如果你认真看了该文章,你也许会明白现在很多组件库的底层实现原理其实就是这样的,比如全局弹出的dialogmodal 框等等。我们要去理解组件库组件实现的思路,而不是一味的复制粘贴。

这个搜索框有很多可以更加优化的地方,你们可以带入自己的思考去想一想。比如

1.如何保存搜索历史?
2.如何实现实时的给出搜索联想

与君共勉才是我的初衷...

关于本文

作者:韩振方

https://juejin.cn/post/7170345858938961957

往期回顾

#

如何使用 TypeScript 开发 React 函数式组件?

#

11 个需要避免的 React 错误用法

#

6 个 Vue3 开发必备的 VSCode 插件

#

3 款非常实用的 Node.js 版本管理工具

#

6 个你必须明白 Vue3 的 ref 和 reactive 问题

#

6 个意想不到的 JavaScript 问题

#

试着换个角度理解低代码平台设计的本质

b3ac34d65c103365bc0b8f72de7c6ec6.gif

这篇关于【Vue】1535- Vue3 如何实现一个全局搜索框的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/832948

相关文章

python使用watchdog实现文件资源监控

《python使用watchdog实现文件资源监控》watchdog支持跨平台文件资源监控,可以检测指定文件夹下文件及文件夹变动,下面我们来看看Python如何使用watchdog实现文件资源监控吧... python文件监控库watchdogs简介随着Python在各种应用领域中的广泛使用,其生态环境也

el-select下拉选择缓存的实现

《el-select下拉选择缓存的实现》本文主要介绍了在使用el-select实现下拉选择缓存时遇到的问题及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录项目场景:问题描述解决方案:项目场景:从左侧列表中选取字段填入右侧下拉多选框,用户可以对右侧

Python pyinstaller实现图形化打包工具

《Pythonpyinstaller实现图形化打包工具》:本文主要介绍一个使用PythonPYQT5制作的关于pyinstaller打包工具,代替传统的cmd黑窗口模式打包页面,实现更快捷方便的... 目录1.简介2.运行效果3.相关源码1.简介一个使用python PYQT5制作的关于pyinstall

使用Python实现大文件切片上传及断点续传的方法

《使用Python实现大文件切片上传及断点续传的方法》本文介绍了使用Python实现大文件切片上传及断点续传的方法,包括功能模块划分(获取上传文件接口状态、临时文件夹状态信息、切片上传、切片合并)、整... 目录概要整体架构流程技术细节获取上传文件状态接口获取临时文件夹状态信息接口切片上传功能文件合并功能小

python实现自动登录12306自动抢票功能

《python实现自动登录12306自动抢票功能》随着互联网技术的发展,越来越多的人选择通过网络平台购票,特别是在中国,12306作为官方火车票预订平台,承担了巨大的访问量,对于热门线路或者节假日出行... 目录一、遇到的问题?二、改进三、进阶–展望总结一、遇到的问题?1.url-正确的表头:就是首先ur

C#实现文件读写到SQLite数据库

《C#实现文件读写到SQLite数据库》这篇文章主要为大家详细介绍了使用C#将文件读写到SQLite数据库的几种方法,文中的示例代码讲解详细,感兴趣的小伙伴可以参考一下... 目录1. 使用 BLOB 存储文件2. 存储文件路径3. 分块存储文件《文件读写到SQLite数据库China编程的方法》博客中,介绍了文

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

JAVA利用顺序表实现“杨辉三角”的思路及代码示例

《JAVA利用顺序表实现“杨辉三角”的思路及代码示例》杨辉三角形是中国古代数学的杰出研究成果之一,是我国北宋数学家贾宪于1050年首先发现并使用的,:本文主要介绍JAVA利用顺序表实现杨辉三角的思... 目录一:“杨辉三角”题目链接二:题解代码:三:题解思路:总结一:“杨辉三角”题目链接题目链接:点击这里

基于Python实现PDF动画翻页效果的阅读器

《基于Python实现PDF动画翻页效果的阅读器》在这篇博客中,我们将深入分析一个基于wxPython实现的PDF阅读器程序,该程序支持加载PDF文件并显示页面内容,同时支持页面切换动画效果,文中有详... 目录全部代码代码结构初始化 UI 界面加载 PDF 文件显示 PDF 页面页面切换动画运行效果总结主

SpringBoot实现基于URL和IP的访问频率限制

《SpringBoot实现基于URL和IP的访问频率限制》在现代Web应用中,接口被恶意刷新或暴力请求是一种常见的攻击手段,为了保护系统资源,需要对接口的访问频率进行限制,下面我们就来看看如何使用... 目录1. 引言2. 项目依赖3. 配置 Redis4. 创建拦截器5. 注册拦截器6. 创建控制器8.