前言:
DevNow 專案中我們使用了 DocSearch 來實現搜尋功能,但是由於有以下的限制:
- 您的網站必須是技術文件或技術部落格。
- 您必須是網站的所有者,或者至少具有更新其內容的許可權
- 您的網站必須公開可用
- 您的網站必須已準備好生產環境。
由於這些條件的限制, DocSearch
只適合用在開源的技術文件或技術部落格中,所以從 DevNow 開源部落格專案的定位來說,還是比較適適合的。為什麼突然想著要最佳化一下搜尋元件呢?
今天心血來潮想在 blog
上找一篇文章,用搜素的功能發現搜不出來😂,搜尋掛了?然後突然想起來之前由於想著在 blog
中可能加一些私有的配置或者嚐鮮的功能,所有 fork
了一份變成 私有專案了,這樣就不符合 DocSearch
的 網站必須是公開的這個限制了。
其實用下來我個人感覺還有以下幾點不是很滿意的地方:
- 配置麻煩,需要在
Algolia
上註冊賬號,然後建立一個應用,然後在專案裡配置一些api-key
資訊。 - 搜尋資訊索引的配置可能需要自己在平臺上定製化一些
config
,開始可能會比較迷茫。 - 索引可能會不及時,預設一週會重新索引一次,所以剛釋出的文章可能無法被搜尋到。
所以一怒之下就想著改一下,之前想著看看其他的方案,之前在在 shadcn/ui
中看到了一個 Command
+ K
的搜尋元件,所以就想著能不能在 DevNow
中也實現一下。這個實現的方案是基於 cmdk 這個庫來實現的, Nextjs
官網應該也是這個實現方案。
整合 shadcn/ui
在這裡還是推薦使用 shadcn/ui 來整合,因為它提供了很多的元件,而且還提供了一些 hooks
來幫助我們實現一些功能。想著後續可能還會用到一些元件,所以就直接整合了,不得不說這個UI庫的樣式真是深得我心啊。
安裝的話直接跟著 文件 來就好,提供了一些常見框架的整合:
:::tip[注意]
在安裝前要先在 tsconfig.json
中配置好 resolve paths
@/*
這個是必須有的,否則 shadcn/ui
無法初始化成功
{
"compilerOptions": {
// ...
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"] // <--- 這個必須有
}
// ...
}
}
:::
安裝 Command
文件在 這裡
pnpm dlx shadcn@latest add command
一個簡單的例子
const Search: FC<Props> = ({ category }) => {
const [open, setOpen] = useState(false);
useEffect(() => {
const down = (e: KeyboardEvent) => {
if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
setOpen((open) => !open);
}
};
document.addEventListener('keydown', down);
return () => document.removeEventListener('keydown', down);
}, []);
return (
<div className='mr-4 hidden items-center lg:flex'>
<Button
onClick={() => setOpen((open) => !open)}
size='sm'
className='h-[32px] bg-muted/50 px-2 py-0 text-sm text-muted-foreground shadow-none hover:bg-accent focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring'
>
Search
<kbd className='pointer-events-none ml-1 inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground opacity-100'>
<span className='text-xs'>⌘</span>K
</kbd>
</Button>
{config.search && (
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder='Type a command or search...' />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
{category.map((item, index) => (
<Fragment key={index}>
<CommandGroup heading={item.label}>
{item.children.map((child, cIndex) => (
<CommandItem
// 這裡需要注意的是:
// 選項點選選中的事件是 onSelect 不是 onClick
onSelect={() => {
window.open(`${location.origin}/posts/${child.id}`, '_blank');
}}
key={cIndex}
className='cursor-pointer'
>
<span>{child.label}</span>
</CommandItem>
))}
</CommandGroup>
{index === category.length - 1 && <CommandSeparator />}
</Fragment>
))}
</CommandList>
</CommandDialog>
)}
</div>
);
};
效果展示:
使用 DocSearch
:
使用 command
替換後:
大家覺得哪個更好呢,我個人覺得新版的會更加簡潔一點。
遇到的問題
第一版的實現到這裡就結束了,然後上線後發現了問題。發現線上的環境突然變卡了,請求也變多了,一看發現堵了很多請求:
然後就是📌定位問題,發現原因是: 當我在 Astro
中整合 React
框架時,如果元件是執行要求在客戶端執行時:
<Search client:only='react' />
在透過 client:only
來標記的元件中獲取所有檔案的標題時,會把所有的請求一遍,這裡我的理解是在客戶端沒有這些資訊,所有執行的時候會構建一份。
這裡如果改下資料來源,將資料傳遞給 Search的話,可以規避掉這個問題。
<Search category={category} client:only='react'>
原文連結:https://www.laughingzhu.cn/posts/cdmk