Vue3 如何實(shí)現(xiàn)一個(gè)全局搜索框

注意:本文不會(huì)馬上教你如何編寫代碼,而是作為一個(gè)引路人,一步一步引導(dǎo)你去理解這個(gè)組件的設(shè)計(jì)思路饲鄙。會(huì)以“假如我是一個(gè)初學(xué)者,如果我在學(xué)習(xí)這個(gè)知識(shí)的時(shí)候圆雁,別人能這樣告訴我,那么我也可以很快的去理解”的角度去講解 帆谍,授人以魚不如授人以漁伪朽。希望你在閱讀本文的時(shí)候可以拓展思路,舉一反三汛蝙。

一. 文件準(zhǔn)備

前期你需要準(zhǔn)備三個(gè)文件烈涮,來完成這個(gè)全局搜索框

SearchBar.ts文件

SearchBar.vue文件

useSearch.ts文件


二. 搜索框的樣式

樣式問題不是本文的重點(diǎn),你可以花費(fèi)五分鐘在SearchBar.vue文件內(nèi)速寫一個(gè)非常簡易的正方形div包裹著一個(gè)input標(biāo)簽即可快速進(jìn)行下面的學(xué)習(xí)窖剑。

但是首先我們需要理清思路坚洽,這個(gè)組件是會(huì)出現(xiàn)在我們頁面的最頂部的,所以它組件內(nèi)部需要用到絕對(duì)布局西土。我們?nèi)earchBar.vue去設(shè)置一個(gè)樣式給最外層的div讶舰,這里其它樣式的寫法使用的是Uno CSS,沒用過的小伙伴也不需要擔(dān)心需了,它只是單純的樣式跳昼,和本文中心內(nèi)容不牽扯。(CSS寫成計(jì)算屬性在這個(gè)場(chǎng)景也毫無特殊意義肋乍,只是單純?cè)O(shè)計(jì)時(shí)考慮多了)




三. 渲染函數(shù)?h?和?render?函數(shù)(重點(diǎn))

打開之前準(zhǔn)備的?SearchBar.ts?文件鹅颊,從?vue?里引入這兩個(gè)函數(shù),并且把在上一步寫好的簡陋版搜索框(SearchBar.vue)引入到這個(gè)文件內(nèi)墓造。


首先我們從官網(wǎng)的介紹堪伍,先看一下這個(gè)函數(shù)的定義。?


可以看出觅闽,這個(gè)函數(shù)第一個(gè)參數(shù)是必填的帝雇,可以是一個(gè)string和Component,這篇文章重點(diǎn)討論參數(shù)為Component的情況谱煤。重點(diǎn)是這個(gè)函數(shù)的返回值摊求,是一個(gè)VNode,這個(gè)你一定不陌生,Virtual Node室叉,看本篇文章的讀者可能對(duì)虛擬dom的原理可能不是那么清楚睹栖,但是我相信你們一定知道它的基本機(jī)制。Vue其實(shí)是先渲染虛擬 dom -->然后 轉(zhuǎn)換成真實(shí) dom茧痕。

先別急著寫代碼野来,我想你可能更清楚這樣的寫法,比如我們前面在SearchBar.vue文件內(nèi)寫的簡單的彈出框踪旷。


整個(gè)組件的樣式都是在Vue提供的<template>組件內(nèi)寫的曼氛,但是你要知道,Vue在底層還是通過調(diào)用h()來完成虛擬dom的構(gòu)建令野。而<template>僅僅只是Vue為了讓你用熟悉的原生html開發(fā)而為你提供的語法糖??而已舀患。(嗯,你可以這樣理解)

那么我們可以根據(jù)上面h()函數(shù)的介紹气破,它接收的第一參數(shù)可以是Component聊浅,那我們這個(gè)SearchBar.vue不就是組件嗎?那如果我不想使用<template>去展示這個(gè)組件的話现使,我是否可以這樣寫呢低匙?h(SearchBar.vue)。沒錯(cuò)碳锈,是的顽冶,你就是可以這樣寫。別忘了h的返回值就是我們想拿到的Vnode售碳,所以按照正確的寫法是這樣的强重。


三. 編寫?SearchBarMaker?構(gòu)造函數(shù)和?present?方法


讓我們回到?SearchBar.ts?文件

首先思考,這個(gè)搜索框一定有一個(gè)出現(xiàn)的函數(shù)贸人,和一個(gè)消失的函數(shù)??竿屹,ok,起名字灸姊,一個(gè)?present拱燃,一個(gè)?dismiss?。


接下來我需要?jiǎng)?chuàng)建出一個(gè)?VNode?力惯,然后想辦法處理成真實(shí)?dom碗誉。經(jīng)過上面的學(xué)習(xí),第一步馬上就可以想到下面的寫法父晶。



下面這位更是重量級(jí)哮缺,render()?函數(shù)。虛擬?dom?有了甲喝,真實(shí)dom?該如何拿到呢尝苇??Vue?為我們提供了這樣一個(gè)函數(shù),這里我們需要重點(diǎn)去看這個(gè)函數(shù)的類型是值,是一個(gè)?RootRenderFunction?類型的糠溜。


這里我們轉(zhuǎn)變一下思路淳玩,我們看一下?render?函數(shù)的第二個(gè)參數(shù)是 一個(gè)?container:HostElement?,然后讓我們打開我們?main.ts?文件非竿,我們跳進(jìn)?mount的定義部分蜕着,



發(fā)現(xiàn)神奇的地方了嗎,我們雖然不知道?HostElement?的類型是什么红柱,但是你知道你?mount?函數(shù)內(nèi)填的參數(shù)是什么了嗎承匣?(忘掉的轉(zhuǎn)頭自覺復(fù)習(xí)官網(wǎng)哈。)

沒錯(cuò)锤悄,就是全局唯一的一個(gè)真實(shí)?dom韧骗,一個(gè)樸實(shí)無華的id叫?app?的?div?元素。


由于篇幅限制零聚,在這里你可以先暫時(shí)簡單的理解宽闲,render函數(shù)會(huì)將你的虛擬dom包裝成一個(gè)真實(shí)dom元素,但是你需要給它一個(gè)真實(shí)的外殼dom來告訴它將虛擬dom渲染到哪個(gè)位置握牧。

ok,拿到一個(gè)包裝后的虛擬 dom 娩梨,接下來就是告訴瀏覽器在哪里渲染這個(gè)元素沿腰。這里我們需要思考??,既然是全局都可以彈出的狈定,并且需要在所有組件之上彈出颂龙。


那么最簡單的方法就是讓它出現(xiàn)在body的第一個(gè)元素,那么它一定會(huì)和我們網(wǎng)頁所有的組件同級(jí)別(tips:通常我們所有的頁面構(gòu)成都會(huì)寫在body內(nèi)的一個(gè)div內(nèi)纽什。什么措嵌?你問我為什么?請(qǐng)打開你的index.html看一下芦缰,你是否忘記了我們的App.vue是掛在這個(gè)真實(shí)的企巢,id為 app的元素內(nèi)的)


那其實(shí)我們的操作的思路就是非常簡單的,當(dāng)我按下全局搜索按鈕让蕾,那么你就在?<div id="app">?的元素之前插入我的組件即可浪规。


ok,到這里我們已經(jīng)可以看到基本效果了探孝,我們來測(cè)試一下笋婿。讓我們?cè)贏pp.vue組件內(nèi)隨便寫一個(gè)按鈕,然后調(diào)用SearchBarCreator實(shí)例身上的present方法顿颅。(maker感覺不是那么合理缸濒,之后我們將SearchBarMaker變更為SeachBarCreator的叫法,僅僅是名字變了而已,邏輯什么的根本沒變哦)庇配。??


效果如下:

https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/66a6f82060a2451da531beaa54506ff7~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp?

到這里?searchBar?已經(jīng)可以呈現(xiàn)在頁面上了讨永,但是我們還不知道怎樣讓它消失卿闹,其實(shí)也非常簡單吏口,我們只需要在合適的時(shí)機(jī)移除這個(gè)?dom?元素即可舟铜。


在這里我們需要知道一點(diǎn)脏里,我們需要將?searchBar?提升到當(dāng)前文件的全局合蔽,不能僅只在?open?中去?new?了衡瓶。


ok,我們測(cè)試一下

https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d3ba620871964046aa5e941f314fb0ac~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp?

四. 優(yōu)化 SearchBarCreator 構(gòu)造函數(shù)的代碼邏輯

寫到這里的時(shí)候包颁,你可能發(fā)現(xiàn)了一個(gè)小問題,當(dāng)我一直去按搜索按鈕的時(shí)候拴驮,它會(huì)出現(xiàn)多個(gè)搜索框绪氛,但是我們希望的是它在全局只能出現(xiàn)一個(gè)搜索框燃逻。換個(gè)角度思考,也就是同一時(shí)間,這個(gè)被我們new出來的SeachBar實(shí)例只能出現(xiàn)一個(gè)握童。思考一下??,我加一個(gè)變量册舞,isShowing 是否正在被展示,如果正在被展示的話障般,那么用戶再次調(diào)用present的時(shí)候藐石,我就去調(diào)用實(shí)例自身的dismiss方法讓它消失,是否可行呢定拟?


測(cè)試一下:

https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/065be5689d5341d8a6c4f96ecaac9287~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp?

OK于微,看來完美解決當(dāng)前的問題了。

五. 編寫全局唯一的調(diào)用實(shí)例

在上面的這種情況下青自,我們已經(jīng)可以在App.vue文件內(nèi)去new一個(gè)實(shí)例來調(diào)用這個(gè)搜索框了株依。但是我們加入現(xiàn)在需要在XXX.vue文件內(nèi)調(diào)用這個(gè)搜索框呢?我難道還需要重新去引入延窜,然后重新new嗎恋腕?nonono,某位大佬說過逆瑞,程序員都是很懶的吗坚,不可能寫這種低級(jí)的重復(fù)代碼的祈远。那么該如何實(shí)現(xiàn)呢

打開我們之前準(zhǔn)備的useSearch.ts文件,我們把之前在App.vue的全局生成的這個(gè) SearchBar 實(shí)例轉(zhuǎn)換思路商源,使它在全局的一個(gè)ts文件內(nèi)生成一個(gè)车份,然后把這個(gè)實(shí)例自身的一些方法封裝成函數(shù),暴露給外部牡彻。那么我就可以在全局任意一個(gè)地方去調(diào)用這個(gè)實(shí)例身上的這兩個(gè)方法扫沼。


讓我們?cè)?App.vue?去試一下。

這是我們之前的?App.vue?文件的調(diào)用方法庄吼。


我們改造一下它缎除。


我們?cè)俅螠y(cè)試一下功能有沒有什么問題

https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/01afbc6ce609421dbec121eb1ffa7827~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp?

如此一來就方便很多了,我們可以在任意位置去調(diào)用這個(gè)“唯一的搜索框”

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

再此之前总寻,我們需要理解一個(gè)概念器罐,注意我們的?main.ts?文件,我們是把誰掛在了全局的那一個(gè)?id='app'

沒錯(cuò)渐行,就是前面我們提到的App.vue組件轰坊。

那么假如我在這個(gè)App.vue組件掛載的時(shí)候,給全局window對(duì)象身上添加一個(gè)鍵盤事件祟印,是不是就可以了呢肴沫?怎么添加呢?其實(shí)非常非常簡單蕴忆,要用到見組合按鍵颤芬,我們就需要使用到“keydown”,具體為什么不是“keypress”套鹅,讀者可以自行查閱這兩者的區(qū)別站蝠,不屬于本文的主要探討內(nèi)容。


這時(shí)候卓鹿,我們先來按一下?command?看看打印的內(nèi)容是什么菱魔。這里重點(diǎn)的內(nèi)容是該鍵盤事件身上的metaKey?屬性。


在這里我們還可以推算出按下?“ctrl”?的事件為?

keydown?事件支持多個(gè)按鍵同時(shí)按下减牺。當(dāng)我們同時(shí)按下 “command” 和 “K” 鍵豌习,會(huì)發(fā)生什么呢存谎?


但是我們發(fā)現(xiàn)好像并沒有?K:true?這個(gè)屬性呀拔疚,那我們?cè)趺慈ヅ袛嗄兀縿e著急接著往下看既荚。

我們可以看到鍵盤事件?event?身上有個(gè)?key?屬性稚失,它的值恰好是字符串類型的?“k”


這里我直接公布寫法恰聘,js 允許我們這樣判斷是否同時(shí)按下兩個(gè)按鍵句各。


我們測(cè)試一下吸占,我們?nèi)グ?App.vue?文件內(nèi)的這兩個(gè)按鈕給去掉


然后再打印一下我們按下?command?和?k?的時(shí)候。


測(cè)試一下:
https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/afe0ebc9c5e14c329b6aa96d20319186~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp?

七. 添加出現(xiàn)的動(dòng)畫

在上面我們可以看到凿宾,這樣突然的出現(xiàn)好像有一絲絲的突兀矾屯。我希望這個(gè)搜索框在出現(xiàn)的時(shí)候,可以有那么一絲絲的平移效果初厚,(類似于下面的效果)該如何做呢件蚕???

https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b57cccc75d3545f3b3dd551c1fda3676~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp?

我這里介紹一種較為簡單的思路,我們?cè)?App.vue?文件的?style?內(nèi)預(yù)設(shè)一個(gè)?Css?動(dòng)畫产禾,并起好名字排作。叫做"searchInput"

然后回到我們?searBar.vue?的組件去,給我們這個(gè)組件最外層的起一個(gè)好聽的名字亚情,我這里就叫做?searchBarWrapper妄痪。


然后回到我們的SearchBar.ts文件內(nèi),也就是放我們SeachBarCreator構(gòu)造函數(shù)的那個(gè)文件內(nèi)楞件。(tips:不是useSearch.ts哦)我這里解釋一下思路衫生,在調(diào)用render函數(shù)后,這個(gè)組件其實(shí)已經(jīng)渲染成為一個(gè)真實(shí)的dom元素履因,只不過我們還沒給它指定渲染的位置障簿。既然是真實(shí)的dom,那么我們就可以通過document.getElementById這個(gè)方法(querySelector同理栅迄,一個(gè)意思)拿到這個(gè)SearchBar.vue組件 站故,接下來我只需要在調(diào)用document.body.insertBefore方法前,給它添加上剛剛我們?cè)贏pp.vue里預(yù)設(shè)好的類名毅舆,searchInput西篓,就完美達(dá)成我們想要的效果了。


注意:style?憋活,這個(gè)點(diǎn)僅僅是類名選擇器岂津,不要忘記了基礎(chǔ)知識(shí)


測(cè)試一下效果:

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ea07aa7fad3b4d8693b441d6e8efd808~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp?

八. 自動(dòng)聚焦

在彈出框的?input?框?qū)崿F(xiàn)自動(dòng)聚焦相比于之前講的就非常簡單了,我在這里一筆帶過了悦即。只需要在?nextTick?中調(diào)用?input?本身的?focus?方法即可吮成。


總結(jié):

之所以不喜歡使用真代碼去寫文章而大量使用截圖的原因是:我自己在搜索到自己想要的文章后,也會(huì)喜歡直接看有沒有最后的成品代碼辜梳,然后直接復(fù)制就拿過去用了粱甫,而往往忽略了自己動(dòng)手去實(shí)現(xiàn)一遍才是真正理解了的過程。

所以我寫代碼的時(shí)候作瞄,盡量不寫特別復(fù)雜的邏輯茶宵,而寫一些很簡單的幾行代碼去實(shí)現(xiàn)某一個(gè)功能。是因?yàn)槲蚁M銈冋嬲龓胱约旱乃伎甲诨樱鸵徊讲襟w會(huì)這個(gè)實(shí)現(xiàn)過程乌庶,從而舉一反三种蝶。

如果你認(rèn)真看了該文章,你也許會(huì)明白現(xiàn)在很多組件庫的底層實(shí)現(xiàn)原理其實(shí)就是這樣的瞒大,比如全局彈出的dialog螃征,modal框等等。我們要去理解組件庫組件實(shí)現(xiàn)的思路透敌,而不是一味的復(fù)制粘貼会傲。

這個(gè)搜索框有很多可以更加優(yōu)化的地方,你們可以帶入自己的思考去想一想拙泽。比如

1.如何保存搜索歷史淌山?

2.如何實(shí)現(xiàn)實(shí)時(shí)的給出搜索聯(lián)想

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

源碼

這里貼出核心代碼SearchBar.ts文件的源碼,希望讀者可以僅作為參考使用顾瞻,希望不要直接復(fù)制粘貼泼疑。

import { h, render } from "vue"

import SearchBar from "./SearchBar.vue"

class SearchBarCreator {

? container: HTMLElement

? appElement: HTMLElement | null

? showing: boolean

? _dismiss: () => void

? constructor() {

? ? this.container = document.createElement("div")

? ? this.showing = false

? ? this.appElement = document.body.querySelector("#app")

? ? this.present.bind(this)

? ? this.dismiss.bind(this)

? ? this._dismiss = this.dismiss.bind(this)

? }

? present() {

? ? if (this.showing) {

? ? ? this.dismiss()

? ? } else {

? ? ? const SearchBar = h(h(SearchBar))

? ? ? render(SearchBar, this.container)

? ? ? const searchBarWrapperDOM =

? ? ? ? this.container.querySelector("#searchBarWrapper")

? ? ? searchBarWrapperDOM?.classList.add("animate-searchInputAnimation")

? ? ? document.body.insertBefore(this.container, document.body.firstChild)

? ? ? this.showing = true

? ? ? this.appElement?.addEventListener("click", this._dismiss)

? ? }

? }

? dismiss() {

? ? if (this.showing && this.container) {

? ? ? render(null, this.container)

? ? ? document.body.removeChild(this.container)

? ? ? this.showing = false

? ? ? this.appElement?.removeEventListener("click", this._dismiss)

? ? } else {

? ? ? console.log("不需要關(guān)閉")

? ? }

? }

}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市荷荤,隨后出現(xiàn)的幾起案子退渗,更是在濱河造成了極大的恐慌,老刑警劉巖蕴纳,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件会油,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡古毛,警方通過查閱死者的電腦和手機(jī)翻翩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來稻薇,“玉大人嫂冻,你說我怎么就攤上這事∪担” “怎么了桨仿?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長案狠。 經(jīng)常有香客問我服傍,道長,這世上最難降的妖魔是什么骂铁? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任吹零,我火速辦了婚禮,結(jié)果婚禮上从铲,老公的妹妹穿的比我還像新娘瘪校。我一直安慰自己澄暮,他們只是感情好名段,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布阱扬。 她就那樣靜靜地躺著,像睡著了一般伸辟。 火紅的嫁衣襯著肌膚如雪麻惶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天信夫,我揣著相機(jī)與錄音窃蹋,去河邊找鬼。 笑死静稻,一個(gè)胖子當(dāng)著我的面吹牛警没,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播振湾,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼杀迹,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了押搪?” 一聲冷哼從身側(cè)響起树酪,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎大州,沒想到半個(gè)月后续语,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡厦画,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年疮茄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片根暑。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡娃豹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出购裙,到底是詐尸還是另有隱情懂版,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布躏率,位于F島的核電站躯畴,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏薇芝。R本人自食惡果不足惜房待,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望茄靠。 院中可真熱鬧售葡,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至付翁,卻和暖如春简肴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背百侧。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來泰國打工砰识, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人佣渴。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓辫狼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親辛润。 傳聞我的和親對(duì)象是個(gè)殘疾皇子予借,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容