
@toss hangul을 이용한 검색 컴포넌트 구현하기
2022.10.14
dev
blog
react
nextjs
지난 13일, 토스에서 실제로 사용하는 라이브러리들을 모은 @toss/slash 라이브러리가 GitHub에 오픈소스로 공개됐다.
FEConf 박서진님의 세션 끝에서 토스 프론트엔드 팀에서 사용하고 있다는 수 많은 라이브러리가 공개된다고 말씀하셨는데, 확인해보니 실제로 수많은 TypeScript/JavaScript 기반의 30개 이상의 npm 패키지들이 오픈됐다.
단숨에 GitHub Trending 2위를 찍고 있었는데 역시 대단하다..
그 중 나의 눈길을 사로잡은 라이브러리는
@toss/hangul
였는데 초성검색(chosungIncludes)이었다. 이를 지금 내 블로그의 검색 기능에 적용해보기로 했다.
필요한 라이브러리를 설치해주자.
yarn add @toss/hangul
AS-IS: 현재 포스트 검색창

현재 내 블로그에서는 초성으로 포스트 검색이 되지 않는다. 자바스크립트의
String.includes()
메서드로 포스트 제목에 검색어가 포함되어있는지를 판단하기 때문에.TO-BE: hangul이 적용된 검색창
import React, { useState, useEffect } from "react"; // ... import { chosungIncludes, hangulIncludes } from "@toss/hangul"; // @toss/hangul import import Layout from "@/components/layout"; import PostCard from "@/components/pages/blog/PostCard"; import SearchBar from "@/components/pages/blog/SearchBar"; import TagSelector from "@/components/pages/blog/TagSelector"; export interface Post { id: string; properties: any; [key: string]: any; } const Blog: React.FC = ({ posts }: any) => { const tags = ["dev", "design", "life", "blog"]; const [selectedTag, setSelectedTag] = useState<string>("All"); const [filteredPosts, setFilteredPosts] = useState<Post[]>(posts); const [searchValue, setSearchValue] = useState(""); const filteredBlogPosts = filteredPosts.filter((frontMatter) => { const postTitle = frontMatter.properties.Name.title[0].plain_text; const searchContent = postTitle; // AS-IS // return searchContent.toLowerCase().includes(searchValue.toLowerCase()); // TO-BE return ( chosungIncludes(searchContent.toLowerCase(), searchValue.toLowerCase()) || // 초성 검색용 hangulIncludes(searchContent.toLowerCase(), searchValue.toLowerCase()) // 한글 검색용 ); }); const displayPosts = filteredPosts.length > 0 && !searchValue ? filteredPosts : filteredBlogPosts; // ... return ( <Layout> ... <div id="top-bar" className="flex justify-between items-center flex-wrap gap-4" > <TagSelector selectedTag={selectedTag} handleTagClick={handleTagClick} tags={tags} /> <SearchBar onChangeHandler={(e) => setSearchValue(e.target.value)} /> </div> {displayPosts.length > 0 ? ( displayPosts.map((post: Post, idx: number) => ( <PostCard key={idx} post={post} /> )) ) : ( <div className="h-80 flex justify-center items-center text-xl"> Noting Found </div> )} </div> ... </Layout> ); }; // ... export default Blog

간단하게 초성으로 검색을 할 수 있게됐다. slash library의 한글 초성 검색 기능 덕분에 직접 구현하지 않아도 되고 아주 편리하다.
├── src │ ├── chosungIncludes.spec.ts │ ├── chosungIncludes.ts // -> chosungIncludes │ ├── constants.ts │ ├── disassemble.spec.ts │ ├── disassemble.ts // -> disassembleHangulToGroups, disassembleHangul │ ├── disassembleCompleteHangulCharacter.spec.ts │ ├── disassembleCompleteHangulCharacter.ts │ ├── hangulIncludes.spec.ts │ ├── hangulIncludes.ts │ ├── index.ts │ ├── josa.spec.ts │ ├── josa.ts │ ├── utils.spec.ts │ └── utils.ts // ├── tsconfig.esm.json └── tsconfig.json
구현이 어떻게 되어있나 확인해보니
chosungIncludes
함수의 경우 disassembleHangulToGroups
를 통해 글자별로 초성/중성/종성 단위로 완전히 분리된 배열을 getFirstConsonants
함수에서 초성을 추출하여(예: 토스
-> 'ㅌㅅ'
) 초성을 포함하고 있는지 확인(includes)하는 식으로 구현되어있다.
또 chosungIncludes는 초성만 검사가 가능하기 때문에 위에 나의 코드에서는
hangulIncludes
도 써야했는데, ‘블로그’는 ‘블록’을 포함할 수 있는 이유는 disassembleHangul
(한글 문자열을 글자별로 초성/중성/종성 단위로 완전히 분리하여, 하나의 문자열로 만듦)을 이용해서 include 검사를 했기 때문이다.이렇게 선언형으로 잘 분리된 라이브러리 코드를 보니 많이 배우게 된다.
이 포스트는 사실 위 사례처럼 slash 라이브러리에서 쓸 수 있는 몇 가지 편리한 유틸 함수들을 소개하고자 적었다.
@toss/hangul
말고도 @toss/react
(유용한 커스텀훅들이 많이 보인다.. 참고가 많이 될 듯🥺), Web Storage를 환경에 구애받지 않고 쓸 수 있도록 하는 @toss/storage
등도 신기하고 코드를 뜯어보는 재미가 있었다.slash 라이브러리를 쭉 살펴보며 다른 좋은 라이브러리를 가져다쓸 수 있는 것이 있다면 찾아보고자 한다.