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