Browser์์ ๊ฒ์์ ์์ฒญํ๋ฉดBack-end์์DB๋ด๋ถ์ ์ ๋ง์ Data๋ค ์์์ ์์ฒญ๋ฐ์keyword๋ฅผ ๊ฐ์ง๊ณfull-scan์ ํ๊ฒ๋๋ค.
๊ธฐ๋ณธ์ ์ธ ์ต์ ์ ์ฃผ์ง ์์ผ๋ฉด ์์์ ๋ถํฐ ํ๋์ฉ ์ฐพ์๋๊ฐ๊ธฐ ๋๋ฌธ์
Data๊ฐ ๋ง์ ์๋ก ์๋๊ฐ ๋๋ฆผ!๋น ๋ฅด๊ฒ ๊ฒ์ํ ์ ์๋ ๋ฐฉ์์?
ํค์๋๋ฅผ ๋์์ฐ๊ธฐ ๊ธฐ์ค์ผ๋ก ์๋ฅธ ๋ค ๊ฒ์์ ์ฉ ํ ์ด๋ธ์ ๋ง๋ ๋ค.
(๊ตฌ๊ธ์์ง์ด ์ฒ์ํ๋ ๋ฐฉ์์ด๋ผ๊ณ ํ๋ค)
์ด๋ ๊ฒ ์๋ฅด๋ ๊ฒ์ ํ ํฌ๋์ด์งํ๋ค, ํ ํฐํํ๋ค๋ผ๊ณ ํจ!
Data๋ฅผ ํน์ ํค์๋๋ค๋ก ๊ตฌ๋ถ์ง์ด, ํด๋นํ๋ ๊ธ๋ค์ ๋ชจ์ ์ค๋ฅธ์ชฝ์ ๋งตํํจ์ผ๋ก์จ ์ญ๋ฐฉํฅ์ผ๋ก ์ ์ฅํ ์ ์๋๋ฐ,
์ด์ ๊ฐ์ ๋ฐฉ์์์ญ์ธ๋ฑ์ค ๋ฐฉ์(Inverted Index)๋๋ ์ญ์์ธ ์ด๋ผ๊ณ ํ๋ค.ํ์ง๋ง ๋งค๋ฒ ์ด๋ ๊ฒ ํ๋ ๊ฒ๋ ๊ท์ฐฎ์ ์ผ์
์๋์ผ๋ก ํด์ฃผ๋ ๊ฒ์ด ์๋?
๊ฒ์์ ์ฉ๋๋น๋ฅผ ๋ง๋ค์ด์ฃผ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ๋ฐ๋ก ์์!
โ Elastic Search(ES)
Disk์ ์ ์ฅ๋๋ ๋ฐฉ์์ผ๋ก ์ปดํจํฐ๊ฐ ๊บผ์ ธ๋ ์ ์ฅ์ด ์ ์ง๋๊ณ ์์ ํ๋ค๋ ํน์ง์ด ์์ง๋ง, ๋น๊ต์ ์๋๋ ์กฐ๊ธ ๋จ์ด์ง๋ค.โ Redis
Memory์ ์ ์ฅ๋๋ ์์์ ์ฅ๋ฐฉ์์ผ๋ก, Disk์ ์ฅ๋ณด๋ค๋ ์์ ์ฑ์ด ๋จ์ด์ง์ง๋ง ์๋๊ฐ ๋น ๋ฅด๋ค.
์๋น์ค ์ ๊ณต ํ ์ ์ ๋ค์ ์ผ์ ๊ฒ์ ํจํด์ด ์๊ธฐ๊ฒ ๋๊ณ , ๊ฒ์๋๊ฐ ๋น๋ฒํ ํค์๋๋ `Disk`์์ ๊บผ๋ด์ค๋ ๊ฒ๋ณด๋ค `Memory ๊ธฐ๋ฐ DB`์ ๋ฃ์ด๋๋ฉด ๊ทธ๋ ๊ทธ๋ ๋ ๋น ๋ฅธ ์ ๊ณต์ด ๊ฐ๋ฅํด์ง๋ค.์ด๋ฐ ๋ฐฉ์์ ๊ฒ์๋ก๊ทธ ์บ์ฑ ์ด๋ผ๊ณ ํ๋ค.
์ฆ, ๊ฒ์์งํ ์, ์บ์ฑ(์ ์ฅ)์ด ๋์ด์๋ค๋ฉด
Redis,
์บ์ฑ๋์ด ์์ง ์์ ๊ธฐ๋ก์Elastic Search๋ฐฉ์ ์ฌ์ฉ
-> ์บ์-์ด์ฌ์ด๋-ํจํด
[์ค์ต section 20-1]
import { useQuery, gql } from "@apollo/client"; import { ChangeEvent, MouseEvent, useState } from "react"; import type { IQuery, IQueryFetchBoardsArgs, } from "../../../src/commons/types/generated/types"; const FETCH_BOARDS = gql` query fetchBoards($page: Int, $search: String) { fetchBoards(page: $page, search: $search) { _id writer title contents } } `; export default function StaticRoutingPage(): JSX.Element { const [search, setSearch] = useState(""); //์ด๊ธฐ๊ฒ์์ด const { data, refetch } = useQuery< Pick<IQuery, "fetchBoards">, IQueryFetchBoardsArgs (FETCH_BOARDS); console.log(data); const onClickPage = (event: MouseEvent<HTMLSpanElement>): void => { void refetch({ page: Number(event.currentTarget.id) }); }; const onChangeSearch = (event: ChangeEvent<HTMLInputElement>): void => { setSearch(event.currentTarget.value); //๊ฒ์ํ ํค์๋๊ฐ ์ฌ๊ธฐ์ ์ ์ฅ }; //๊ฒ์๋ฒํผ ๋๋ฅด๋ฉด useQuery๋ฅผ refetchํ๋ค. const onClickSearch = (): void => { void refetch({ search: search, page: 1 }); //์ฃผ์ํ ์ ! ํด๋น๊ฒ์์ด์ ๋ํ 1ํ์ด์ง๋ฅผ ๊ฐ์ ธ์ค๋ผ๋ ๊ฒ์! }; //๊ฒ์ํ ํค์๋๋ก ๋ฆฌํจ์น ํ๊ธฐ (๋ค์ ๊ฐ์ ธ์ค๊ธฐ) return ( <div> ๊ฒ์์ด์ ๋ ฅ: <input type="text" onChange={onChangeSearch} /> <button onClick={onClickSearch}>๊ฒ์</button> {data?.fetchBoards.map((el) => ( <div key={el._id}> <span style={{ margin: "10px" }}>{el.title}</span> <span style={{ margin: "10px" }}>{el.writer}</span> </div> ))} {new Array(10).fill("์ฒ ์").map((_, index) => ( <span key={index + 1} id={String(index + 1)} onClick={onClickPage}> {index + 1} </span> ))} </div> ); }
onClickSearchํจ์๋(๊ฒ์๋ฒํผ)ํด๋น๊ฒ์์ด์ ๋ํ ์ผํ์ด์ง๋ฅผ ๊ฐ์ ธ์ค๋ผ๋ ๋ป!
refetch๋ฅผ ํ๋ฒ ํ ๊ฒ๋ค์ ์ ์ฅ์ด ๋์ด์๋ค.
๋ฐ๋ผ์ onClickPage๋ฅผ ์คํํ๋ฉด ํ์ด์ง๋ฐ์ ์์ฑํ์ง ์์๋๋ฐ๋
์ ๋ ฅํ์ง์์ search๊ฐ ๋ค์ด๊ฐ์์(์ด๋ฏธ ๊ธฐ์กด์ ์๋ search๊ฐ ๊ฐ์ด ๋ค์ด๊ฐ)
export default function StaticRoutingPage(): JSX.Element { const [search, setSearch] = useState(""); //์ด๊ธฐ๊ฒ์์ด const { data, refetch } = useQuery< Pick<IQuery, "fetchBoards">, IQueryFetchBoardsArgs (FETCH_BOARDS); console.log(data); const onClickPage = (event: MouseEvent<HTMLSpanElement>): void => { void refetch({ page: Number(event.currentTarget.id) }); }; const onChangeSearch = (event: ChangeEvent<HTMLInputElement>): void => { void refetch({ search: event.currentTarget.value, page: 1 }); setSearch(event.currentTarget.value); //๊ฒ์ํ ํค์๋๊ฐ ์ฌ๊ธฐ์ ์ ์ฅ };๋ง์ฝ ๊ฒ์์ด๊ฐ ๋ณ๊ฒฝ๋๋ฉด ๋ณ๊ฒฝ๋ ๊ฒ์์ด๋ก ๋ ๋ฆฌ๋ฉด ๋จ. ๊ทธ ๊ฐ ๊ทธ๋๋ก refetch!
ํ์ง๋ง ์ด๋ฐ๋ฐฉ์์ ๋ฌธ์ ๊ฐ ์์
์ด๋ค ๋ฌธ์ ๋?
API์์ฒญ์ด ์์ฒญ ๋๊ฐ๊ณ ์์
onChange ์์์ refetch๊ฐ ๋๊ฐ๋ฏ๋กpage๋ฅผ ๋ณ๊ฒฝํ๋ฉฐrefetch๋ ๋,
state๋ก ๊ด๋ฆฌํ๋ ๊ฒ์ inputํค์๋ ๊ฐ์ด ๊ฒ์์ ๋๋ฅด์ง ์์๋ ๊ฒ์๋์ด
ํ๋ํ๋ ์ ๋ ฅํ ๋๋ง๋ค refetch์์ฒญ์ด ๋ฐ์ํ๋ ๊ฒ!
๋ง์ฝ ์ด๋ฐํ๋์ 1์ต๋ช ์ด ํ๋ค๊ณ ํ๋ค๋ฉด ??!!
์์ฒญ ๋ํ ๋ฌด์ํ ๋ง์์ง๊ณ ๊ทธ๋งํผ ๋คํธ์ํฌ ๋น์ฉ ๋ฑ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
์ด๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ๋๋ฐ์ด์ฑ๊ณผ ์ฐ๋กํ๋ง์ ์ฌ์ฉํ ์ ์๋ค.
๋๋ฐ์ด์ฑ: ์ฃผ๋ก ๊ทธ๋ฃน์์ ๋ง์ง๋ง, ํน์ ์ฒ์์ ์ฒ๋ฆฌ๋ ํจ์๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ์
๋ํ์ ์์ ) ๊ฒ์๊ธฐ๋ฅ
์ฐ๋กํ๋ง: ์ฐ์ด์ด ๋ฐ์ํ ์ด๋ฒคํธ์ ๋ํด ์ผ์ ํdelay๋ฅผ ํฌํจ ์์ผ, ์ฐ์์ ์ผ๋ก ๋ฐ์ํ๋ ์ด๋ฒคํธ๋ ๋ฌด์ํ๋ ๋ฐฉ์
์ฆ, ์ง์ ํdelay๋์ ํธ์ถ๋ ํจ์๋ ๋ฌด์ํ๋ค.
๋ํ์ ์์ ) ์คํฌ๋กค ๊ธฐ๋ฅ
Lodash : ์๋ฐ์คํฌ๋ฆฝํธ์ ์ ํธ๋ฆฌํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ (์ ์ฉํ ๋ด์ฅํจ์ ๋ค๋๋ณด์ )
- ์ค์น ๋ช ๋ น์ด
yarn add lodash
yarn add -D @types/lodash
โ Debounce
: ๋ฐ๋ณต์ ์ธ ๋์์ ๊ฐ์ ์ ์ผ๋ก ๋๊ธฐํ๋ ๊ฒ(์ค๊ฐ๊ณผ์ ์ ์์ ๊ณ ๊ฒฐ๊ณผ๋ง ํ๋ฒ์ ์คํ)
- debounce( ์ฝ๋ฐฑํจ์ (์คํ์ํค๊ณ ์ถ์ ํจ์) , ์๊ฐ)
- setTimeout๊ณผ ์ฌ์ฉ๋ฐฉ๋ฒ ๊ฐ์.
- ํด๋น ์๊ฐ ๋์ ์๋ฌด ์ผ๋ ํ์ง ์์์ ๋ ์ฝ๋ฐฑํจ์๋ฅผ ์คํ์ํจ๋ค.
- ์ด ์๊ฐ์ ttl์ด๋ผ๊ณ ํ๋ค. ๊ฐ๊ธ์ ์งง๊ฒ ํด์ฃผ๋ ๊ฒ์ด ์ข์.
์ฆ, ์ ๋ ฅ ing๋ฉด ํจ์ ์คํX
์ ๋ ฅ is done ์ด๋ฉด ๊ทธ๋ ํจ์ ๊ฒฐ๊ณผ ๋ณด์ฌ์ค.
[์ค์ต section 20-2]//Debounce๋ถ๋ฌ์ค๊ธฐ import { debounce } from 'lodash'; // or import _ from "lodash"; export default function StaticRoutingPage(): JSX.Element { // const [search, setSearch] = useState(""); //์ด๊ธฐ๊ฒ์์ด const [keyword, setKeyword] = useState("");//ํค์๋ ๊ฐ์ ๋ฐ๋ก ์ ์ฅ์์ผ์ฃผ๋ `state`๋ฅผ ๋ถ๋ฆฌ const { data, refetch } = useQuery< Pick<IQuery, "fetchBoards">, IQueryFetchBoardsArgs (FETCH_BOARDS); const onClickPage = (event: MouseEvent<HTMLSpanElement>): void => { void refetch({ page: Number(event.currentTarget.id) }); }; //์ํ๋ ๊ธฐ๋ฅ debounce๋ก ๊ฐ์ธ์ฃผ๊ธฐ const getDebounce = _.debounce((value) => { void refetch({ search: value, page: 1 }); setKeyword(value); //์ฒ ์๋ผ๋ ๊ฐ์ด ํค์๋์ ์ ์ฅ๋จ. }, 1000); //1์ด const onChangeSearch = (event: ChangeEvent<HTMLInputElement>): void => { // void refetch({ search: event.currentTarget.value, page: 1 }); // setSearch(event.currentTarget.value); getDebounce(event.currentTarget.value); }; // const onClickSearch = (): void => { // void refetch({ search: search, page: 1 }); // }; return ( <div> ๊ฒ์์ด์ ๋ ฅ: <input type="text" onChange={onChangeSearch} />
ํ์ด์ง๋ค์ด์ ์ ๋ ๊ฐ์ด ์ ์ฉ ์์ผ์ค์ผ ํ๋ค. search๊ฐ ๋์ผ ํ์ด์ง๋ค์ด์ ๋ ์๋ํ๊ธฐ๋๋ฌธ์ setSearch ์ฎ๊ฒจ์ค๋ค.
๋ง์ฝ ๊ฒ์ํ ํค์๋์๋ง ์๊น ๋ฑ์ ํจ๊ณผ๋ฅผ ์ ์ฉํ๊ณ ์ถ๋ค๋ฉด ์ด๋ป๊ฒ ํด์ผํ ๊น?
.split๋ฉ์๋๋ฅผ ์ฌ์ฉํด์ ๋๋๋ค๋ฉด?
-> ๋ด๊ฐ ์ํ๋ ๊ฑด "์ฒ ์"๋ง ์ธ๋ฐ ๊ฐ๊น์ง ๋ฐ์ ๋ถ๋ฆฌํ ์ ๊ฐ ์์.
๋๋ค๋ฅธ ํค์๋๊ฐ ํ์ํจ!
์ด๋ด ๋ ์ฌ์ฉํ๋ ๊ฒ์ด ์ํฌ๋ฆฟ ์ฝ๋!
์ ์ ๊ฐ ๋ชจ๋ฅด๋ ๋ฐฉ๋ฒ์ผ๋ก ์ฝ๋๋ฅผ ์์ฑํ๊ฒ!
(์ ์ ๊ฐ ์ค์๋ก๋ผ๋ ๋ฐ๋ผ ํ ์ ์๊ฒ๋)
[์ค์ต section 20-3]import { useQuery, gql } from "@apollo/client"; import { ChangeEvent, MouseEvent, useState } from "react"; import type { IQuery, IQueryFetchBoardsArgs, } from "../../../src/commons/types/generated/types"; import _ from "lodash"; import { v4 as uuidv4 } from "uuid"; const FETCH_BOARDS = gql` query fetchBoards($page: Int, $search: String) { fetchBoards(page: $page, search: $search) { _id writer title contents } } `; export default function StaticRoutingPage(): JSX.Element { const [keyword, setKeyword] = useState(""); const { data, refetch } = useQuery< Pick<IQuery, "fetchBoards">, IQueryFetchBoardsArgs (FETCH_BOARDS); const onClickPage = (event: MouseEvent<HTMLSpanElement>): void => { void refetch({ page: Number(event.currentTarget.id) }); }; const getDebounce = _.debounce((value) => { void refetch({ search: value, page: 1 }); setKeyword(value); }, 1000); const onChangeSearch = (event: ChangeEvent<HTMLInputElement>): void => { getDebounce(event.currentTarget.value); }; return ( <div> ๊ฒ์์ด์ ๋ ฅ: <input type="text" onChange={onChangeSearch} /> {/* <button onClick={onClickSearch}>๊ฒ์</button> */} {data?.fetchBoards.map((el) => ( <div key={el._id}> <span style={{ margin: "10px" }}> {el.title // .replaceAll("์ฒ ์","@#$์ฒ ์@#$") .replaceAll(keyword, `@#$${keyword}@#$`) .split("@#$") .map((el) => ( <span key={uuidv4()} style={{ color: el === keyword ? "red" : "black" }} {el} </span> // .map((el)=><span style={{color: el === "์ฒ ์" ? "red" : "black"}}>{el}</span>{ ))} </span> {/* ํ๋์ ํ์ดํ์ ๋๊ฐ์ span์ผ๋ก ๋๋๋ค. split */} <span style={{ margin: "10px" }}>{el.writer}</span> </div> ))} {new Array(10).fill("์ฒ ์").map((_, index) => ( <span key={index + 1} id={String(index + 1)} onClick={onClickPage}> {index + 1} </span> ))} </div> ); }-> ์ฌ๋ฌ๊ฐ ์์ ์ ๋ ์์ผ๋ map์ key ๋ฑ์ ๊ณ ์ ํ ๊ฐ์ ๋ฃ์ด์ค ๋ ์ฌ์ฉํ ์ ์๋ uuid์ฌ์ฉ!
- ์ค์น๋ช ๋ น์ด
yarn add uuid
yarn add --dev @types/uuid