μ°λ¦¬κ° νμμ μΉμμ κ²μ
μ ν λ, μ»΄ν¨ν° λ΄λΆμμλ μ΄λ€ νλ‘μΈμ€λ‘ μμ§μΌκΉ?
κΈ°λ³Έμ μΌλ‘ λΈλΌμ°μ μμ κ²μ
μ νλ©΄, λ°±μλμμλ λ°μ΄ν°λ² μ΄μ€ μμ λ°μ΄ν°λ€μ μ€μΊνλ€. μ΄λ° λ°©μμ λ°μ΄ν°κ° λ§μΌλ©΄ λ§μμλ‘ μ²λ¦¬ μλκ° λλ €μ§κ² λλ€.
μ΄λ₯Ό 보μνκ³ μ λμ¨ κ²μ΄ μμΈλ±μ€ λ°©μ(μμμΈ λ°©μ= inverted index)
μ΄λ€.
λ°μ΄ν°λ² μ΄μ€μμ λ°μ΄ν°λ€μ λμ΄μ°κΈ°λ₯Ό κΈ°μ€μΌλ‘ μλ₯Έ λ€μμ(ν ν°ν = ν ν¬λμ΄μ§) κ²μ μ μ© ν μ΄λΈμ λ§λ€μ΄μ μλ₯Έ ν ν°λ€μ λΆλ₯νκ³ , κ²μν ν€μλκ° λ€μ΄κ° λ°μ΄ν°λ€λ§ λͺ¨μμ λλ €μ£Όλ λ°©μμ λ§νλ€.
νμ§λ§ μμ λ°©μμ κ²μμ ν λλ§λ€ μ΄λ£¨μ΄μ ΈμΌ νκΈ° λλ¬Έμ κ½€ λ²κ±°λ‘λ€λ λ¨μ μ΄ μμλ€. λ°λΌμ μ΄ κ³Όμ μ μ½κ² μννκΈ° μν΄ μλΌμ€ν± μμΉ(Elasticsearch)
λΌλ λꡬλ₯Ό μ¬μ©νκΈ° μμνλ€.
μλΌμ€ν± μμΉλ λ°μ΄ν°λ₯Ό Diskμ μ μ₯νλ€. Diskμ μ μ₯νλ©΄ μꡬμ μ₯μ κ°λ₯νμ§λ§ Disk IO(Input/Output)κ° λ리λ€λ λ¨μ μ΄ μλ€.
λ°λ©΄, Redis
μ μ μ₯νλ λ°©λ²λ μλ€. Redisλ₯Ό νμ©νλ λ°©λ²μ μΊμ-μ΄μ¬μ΄λ ν¨ν΄ μ€ νλμ΄λ€. Redisλ RAM μ μ₯ λ°©μμ μ¬μ©νλ€. μμ μ μ₯ λ°©μμ΄κΈ° λλ¬Έμ Disk μ μ₯λ³΄λ€ μμ μ±μ λ¨μ΄μ§μ§λ§ μλκ° λΉ λ₯΄λ€λ μ₯μ μ΄ μλ€.
μλΉμ€κ° λμ€κ³ μκ°μ΄ νλ₯΄λ©΄, κ²μμ μ΄λμ λ μΌμ ν ν¨ν΄μ΄ μκΈ°κ² λλ€. μ¬λλ€μ΄ μμ£Ό κ²μνλ κ²λ€μ κ²μλ§λ€ Diskμμ κΊΌλ΄μ€λ κ²λ³΄λ€λ Redisμ κ°μ λ©λͺ¨λ¦¬μ μ μ₯νλ©΄ λ λΉ λ₯΄κ² κ²μκ²°κ³Όλ₯Ό μ 곡ν μ μλ€!
μ’λ ꡬ체μ μΌλ‘ μ€λͺ
μ νμλ©΄, κ²μμ νμ λ Redisμ κΈ°μ‘΄μ κ²μνλ κ²μ΄ μλμ§ μ°Ύμλ³΄κ³ μμΌλ©΄ μλΌμ€ν± μμΉμ κ°μ κΈ°λ₯μ ν΅ν΄ λ§λ€μ΄λμ λ°μ΄ν°λ₯Ό κ°μ Έμμ μ°κ³ , Redisμ μμμ μ₯(μΊμ±
)νλ€. μ¦, μΊμ±
λμ΄ μλ κ²μ Redis, μΊμ±λμ΄ μμ§ μμ κ²μ Elastic Search λ°©μμ΄ μ¬μ©λλ κ²μ΄λ€.
μ¬κΈ°μ μμμ μ₯νλ μκ°μ TTL
μ΄λΌκ³ νλ€. μμμ μ₯ μκ°μ μν©μ λ°λΌ λ€λ₯΄λ€. 짧μ κΈ°κ° μμ λ§μ΄ κ²μλλ κ²μ TTLμ΄ μ§§μ νΈμ΄λ€.
μμ μ λ§λ€μλ κ²μν λͺ©λ‘ νμ΄μ§μ κ²μ κΈ°λ₯μ μΆκ°νλ€.
search
λ₯Ό λ£μ΄μ€λ€.const FETCH_BOARDS = gql`
query fetchBoards($page: Int, $search: String) {
fetchBoards(page: $page, search: $search) {
_id
writer
title
contents
}
}
`;
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>
);
}
search
λΌλ stateλ₯Ό λ§λ€μ΄μ£Όκ³ , onChangeSearch ν¨μμλ κ²μνμ λ, μ
λ ₯ν κ°μ setStateλ‘ μ€μ νλ€. onClickSearch ν¨μμλ κ²μ ν ν€μλκ° μΌμΉνλ κ²μκΈλ€μ΄ λΈλΌμ°μ μ λμ€λλ‘ refetchνλ€.export default function StaticRoutingPage(): JSX.Element {
const [search, setSearch] = useState("");
const { data, refetch } = useQuery(FETCH_BOARDS);
const onClickPage = (event: MouseEvent<HTMLSpanElement>): void => {
void refetch({
page: Number(event.currentTarget.id),
});
};
const onChangeSearch = (event: ChangeEvent<HTMLInputElement>): void => {
setSearch(event.currentTarget.value);
};
const onClickSearch = (): void => {
void refetch({ search: search, page: 1 });
// κ²μ ν€μλκ° λ€μ΄κ° κ²μκΈμ 10κ°(1νμ΄μ§) κ°μ Έμ€λλ‘ ν¨.
};
κ²μ κΈ°λ₯μ μΆκ°ν΄λ³΄λ, κ²μμ΄λ₯Ό μ λ ₯ν λλ§λ€ onchange, refetchκ° μ΄λ£¨μ΄μ§λ€λ λ¬Έμ μ μ΄ μλ€. μλ₯Ό λ€μ΄ "μλ "μ΄λΌλ κ²μμ΄λ₯Ό μ λ ₯νλ€λ©΄, 'γ ', 'γ 'λ₯Ό μ λ ₯ν λλ§λ€ api μμ²μ 보λ΄μ λ§μ½ λ€μμ μ¬λμ΄ κ²μμ ν΄μ μ μλμ΄ λμ΄λλ€λ©΄ λ¬Έμ κ° λ°μν μ μλ€.
μ΄λ₯Ό ν΄κ²°νκΈ° μν΄μλ λλ°μ΄μ±μ νμ©νλ©΄ λλ€!
λλ°μ΄μ±μ΄λ, μ°μ΄μ΄ λ°μν μ΄λ²€νΈλ₯Ό νλμ κ·Έλ£ΉμΌλ‘ λ¬Άμ΄μ μ²λ¦¬νλ λ°©μμ΄λ€. λ§μ§λ§ νΈμΆμ΄ λ°μν νμ μΌμ μκ°μ΄ μ§λ λκΉμ§ μΆκ° μ λ ₯μ΄ μμ λ μ€νλλ€.
μ°λ‘νλ§μ μ°μ΄μ΄ λ°μν μ΄λ²€νΈμ λν΄ μΌμ ν delayλ₯Ό ν¬ν¨μμΌμ μ°μμ μΌλ‘ λ°μνλ μ΄λ²€νΈλ 무μνλ λ°©μμΌλ‘ μ¬μ©λλ€. μ°λ‘νλ§μ 무νμ€ν¬λ‘€μ μ£Όλ‘ μ°μΈλ€. νΉμ μκ° μ΄λ΄μ μΆκ° μ€ν¬λ‘€μ΄ μ΄λ£¨μ΄μ Έλ μΆκ°μ μΈ refetchλ μ€νλμ§ μλλ€.
λλ°μ΄μ±: νΉμ μκ° μ΄λ΄, μΆκ° μ λ ₯ μμ μ, λ§μ§λ§ 1νλ§ μ€ν
μ°λ‘νλ§: νΉμ μκ° μ΄λ΄, μΆκ° μ λ ₯ μμ΄λ, μ²μ 1νλ§ μ€ν
λλ°μ΄μ±μ μλ₯Ό λ€λ©΄, "μ² μ"λΌκ³ κ²μνμ λ, "μ² μ"λ₯Ό μ λ ₯νκ³ νΉμ μκ°λμ μΆκ° μ λ ₯μ΄ μλ€λ©΄, κ·Έλ refetchλ₯Ό μ€ννλ€.
λλ°μ΄μ±κ³Ό μ°λ‘νλ§μ lodashμ λΌμ΄λΈλ¬λ¦¬κ° μμ΄μ, μ΄λ₯Ό νμ©ν΄μ κ²μ λ²νΌ μμ΄ μ λ ₯μ°½μ ν€μλ μ λ ₯ μ λ°λ‘ κ²μμ΄ λλλ‘ νλ€. λ°©λ²μ λ€μκ³Ό κ°λ€.
lodash
μ€μΉν΄μ, import ν΄μ£ΌκΈ°yarn add lodash
yarn add --Dev @types/lodash // νμ
μ€ν¬λ¦½νΈ μ¬μ© μ μΆκ° μ€μΉ νμ
import _ from "lodash";
getDebounce
λ§λ€μ΄μ£ΌκΈ°const getDebounce = _.debounce((value) => {
// onChangeSearch μ€νλμμ λ, event.currentTarget.value κ°μ΄ value μλ¦¬λ‘ λ€μ΄μ€κ² λ¨.
void refetch({ search: value, page: 1 });
// μ
λ ₯κ°(value)λ₯Ό λ°λ‘ search ν΄μ£Όλ©΄ λλ€. search state νμ μμ!
}, 500); // debounce μΆκ° μ
λ ₯μ΄ μλμ§ μΈ‘μ νλ μκ°
event.currentTarget.value
κ° λ£μ΄μ£ΌκΈ°(μ
λ ₯λ κ²μμ΄ λλ°μ΄μ±λλλ‘ ν¨)const onChangeSearch = (event: ChangeEvent<HTMLInputElement>): void => {
getDebounce(event.currentTarget.value);
};
κ²μν ν€μλλ§ μμμ΄ λ°λμ΄μ κ²°κ³Όκ° λμ€λλ‘ νκ³ μΆμ λμλ μ΄λ»κ² ν΄μΌν κΉ?
μν¬λ¦Ώ μ½λ
λ₯Ό μ¬μ©ν΄μ κ²μ ν€μλμ, ν€μλκ° μλ ꡬκ°μΌλ‘ λλλ€. "μ² μκ° μ μ¬μ λ¨Ήμλ€".replace("μ² μ", "@#$μ² μ@#$")
// '@#$μ² μ@#$κ° μ μ¬μ λ¨Ήμλ€'
"μ² μκ° μ μ¬μ λ¨Ήμλ€".replace("μ² μ", "@#$μ² μ@#$").split("@#$")
// ['', 'μ² μ', 'κ° μ μ¬μ λ¨Ήμλ€']
const [keyword, setKeyword] = useState("");
setKeyword(value);
<span style={{ margin: "10px" }}>
{el.title
.replaceAll(keyword, `@#$${keyword}@#$`)
// ν λ¬Έμ₯μ keywordκ° μ¬λ¬ λ² λμ¬ μλ μμΌλκΉ replace λμ replaceAll μ¬μ© //
.split("@#$")
.map((el) => (
<span
key={uuidv4()} // el._idλ₯Ό μ¬μ©ν μ μκΈ° λλ¬Έμ uuid μ¬μ©. μ±λ₯μ΅μ ν λ©΄μμλ μ’μ§ μμ λ°©λ²..
style={{ color: el === keyword ? "red" : "black" }}
>
{el}
</span>
))}
</span>
μ€λμ κ²μκΈ°λ₯μ λν΄ κ³΅λΆνλ€. κ°μ₯ λ² μ΄μ§ν κ²μ κΈ°λ₯λΆν° λ°°μλ΄€λλ°, κ·Έλ¬λ©΄ κΌ κ²μ ν€μλκ° ν¬ν¨λμ§ μμλ κ΄λ ¨λ μ μ¬ν λ°μ΄ν°λ€μ΄ κ²μλλ κ²½μ°λ λ§μλ° μ΄λ¬ν κ²½μ°μλ νλ‘ νΈμλμμλ μ΄λ»κ² λΈλΌμ°μ μ ꡬννλ κ±΄μ§ κΆκΈνλ€. μλ§ ν¨μ¬ 볡μ‘ν λ‘μ§μ΄κ² μ§..? κ°λ©΄ κ°μλ‘ λ°±μλκ° μ λ¬νλ λ°μ΄ν°λ₯Ό νμ©νλ λ²μκ° λμ΄λλ κ² κ°μμ λΏλ―νλ€!