useMemo ๋Š” ์žฌ๋ Œ๋”๋ง ์‚ฌ์ด์— ๊ณ„์‚ฐ ๊ฒฐ๊ณผ๋ฅผ ์บ์‹ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” Rect Hook ์ž…๋‹ˆ๋‹ค.

const cachedValue = useMemo(calculateValue, dependencies)

๋ ˆํผ๋Ÿฐ์Šค

useMemo(calculateValue, dependencies)

์ปดํฌ๋„ŒํŠธ์˜ ์ตœ์ƒ์œ„ ๋ ˆ๋ฒจ์— ์žˆ๋Š” โ€˜useMemoโ€™๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์žฌ๋ Œ๋”๋ง ์‚ฌ์ด์˜ ๊ณ„์‚ฐ์„ ์บ์‹ฑํ•ฉ๋‹ˆ๋‹ค.

import { useMemo } from 'react';

function TodoList({ todos, tab }) {
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab]
);
// ...
}

์•„๋ž˜๋กœ ์ด๋™ํ•˜์—ฌ ๋” ๋งŽ์€ ์˜ˆ์‹œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.

๋งค๊ฐœ๋ณ€์ˆ˜

  • calculateValue: ์บ์‹ฑํ•˜๋ ค๋Š” ๊ฐ’์„ ๊ณ„์‚ฐํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. ์ˆœ์ˆ˜ํ•ด์•ผ ํ•˜๋ฉฐ ์ธ์ž๋ฅผ ๋ฐ›์ง€ ์•Š๊ณ , ๋ชจ๋“  ํƒ€์ž…์˜ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. React๋Š” ์ดˆ๊ธฐ ๋ Œ๋”๋ง ์ค‘์— ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ ๋ Œ๋”๋ง์—์„œ, React๋Š” ๋งˆ์ง€๋ง‰ ๋ Œ๋”๋ง ์ดํ›„ dependencies๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์„ ๋•Œ ๋™์ผํ•œ ๊ฐ’์„ ๋‹ค์‹œ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š๋‹ค๋ฉด calculateValue๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, ๋‚˜์ค‘์— ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

  • dependencies: calculateValue ์ฝ”๋“œ ๋‚ด์—์„œ ์ฐธ์กฐ๋œ ๋ชจ๋“  ๋ฐ˜์‘ํ˜• ๊ฐ’๋“ค์˜ ๋ชฉ๋ก์ž…๋‹ˆ๋‹ค. ๋ฐ˜์‘ํ˜• ๊ฐ’์—๋Š” props, state์™€ ์ปดํฌ๋„ŒํŠธ ๋ฐ”๋””์— ์ง์ ‘ ์„ ์–ธ๋œ ๋ชจ๋“  ๋ณ€์ˆ˜์™€ ํ•จ์ˆ˜๊ฐ€ ํฌํ•จ๋ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ linter๊ฐ€ React์šฉ์œผ๋กœ ์„ค์ •๋œ ๊ฒฝ์šฐ ๋ชจ๋“  ๋ฐ˜์‘ํ˜• ๊ฐ’์ด ์˜์กด์„ฑ์œผ๋กœ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์„ค์ •๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜์กด์„ฑ ๋ชฉ๋ก์€ ์ผ์ •ํ•œ ์ˆ˜์˜ ํ•ญ๋ชฉ์„ ๊ฐ€์ ธ์•ผ ํ•˜๋ฉฐ, [dep1, dep2, dep3]์™€ ๊ฐ™์ด ์ธ๋ผ์ธ ํ˜•ํƒœ๋กœ ์ž‘์„ฑ๋ผ์•ผ ํ•ฉ๋‹ˆ๋‹ค. React๋Š” Object.is ๋น„๊ต๋ฅผ ํ†ตํ•ด ๊ฐ ์˜์กด์„ฑ ๋“ค์„ ์ด์ „ ๊ฐ’๊ณผ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.

๋ฐ˜ํ™˜๊ฐ’

์ดˆ๊ธฐ ๋ Œ๋”๋ง์—์„œ useMemo๋Š” ์ธ์ž ์—†์ด calculateValue๋ฅผ ํ˜ธ์ถœํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ ๋ Œ๋”๋ง์—์„œ, ๋งˆ์ง€๋ง‰ ๋ Œ๋”๋ง์—์„œ ์ €์žฅ๋œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜(์ข…์†์„ฑ์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ), calculateValue๋ฅผ ๋‹ค์‹œ ํ˜ธ์ถœํ•˜๊ณ  ๋ฐ˜ํ™˜๋œ ๊ฐ’์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

์ฃผ์˜ ์‚ฌํ•ญ

  • useMemo๋Š” Hook์ด๋ฏ€๋กœ ์ปดํฌ๋„ŒํŠธ์˜ ์ตœ์ƒ์œ„ ๋ ˆ๋ฒจ ๋˜๋Š” ์ž์ฒด Hook์—์„œ๋งŒ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋ณต๋ฌธ์ด๋‚˜ ์กฐ๊ฑด๋ฌธ ๋‚ด๋ถ€์—์„œ๋Š” ํ˜ธ์ถœํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋งŒ์ผ ํ˜ธ์ถœ์ด ํ•„์š”ํ•˜๋‹ค๋ฉด ์ƒˆ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ถ”์ถœํ•˜๊ณ  ์ƒํƒœ๋ฅผ ๊ทธ ์•ˆ์œผ๋กœ ์˜ฎ๊ฒจ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • Strict Mode์—์„œ๋Š” , React๋Š” ์‹ค์ˆ˜๋กœ ๋ฐœ์ƒํ•œ ์˜ค๋ฅ˜๋ฅผ ์ฐพ๊ธฐ ์œ„ํ•ด ๊ณ„์‚ฐ ํ•จ์ˆ˜๋ฅผ ๋‘ ๋ฒˆ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋งŒ ๋™์ž‘ํ•˜๋Š” ๋ฐฉ์‹์ด๋ฉฐ, ์‹ค์ œ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—๋Š” ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. (์›๋ž˜ ๊ทธ๋ž˜์•ผ ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ) ์—ฐ์‚ฐ ํ•จ์ˆ˜๊ฐ€ ์ˆœ์ˆ˜ํ•˜๋‹ค๋ฉด, ๋กœ์ง์—๋Š” ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ˜ธ์ถœ ๊ฒฐ๊ณผ ์ค‘ ํ•˜๋‚˜๋Š” ๋ฌด์‹œ๋ฉ๋‹ˆ๋‹ค.
  • React๋Š” ์บ์‹ฑ ๋œ ๊ฐ’์„ ๋ฒ„๋ ค์•ผ ํ•  ํŠน๋ณ„ํ•œ ์ด์œ ๊ฐ€ ์—†๋Š” ํ•œ ๋ฒ„๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๊ฐœ๋ฐœ ๋‹จ๊ณ„์—์„œ ์ปดํฌ๋„ŒํŠธ ํŒŒ์ผ์„ ํŽธ์ง‘ํ•  ๋•Œ React๋Š” ์บ์‹œ๋ฅผ ๋ฒ„๋ฆฝ๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ๊ณผ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ ๋ชจ๋‘์—์„œ๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ดˆ๊ธฐ ๋งˆ์šดํŠธ ์ค‘์— ์ผ์‹œ ์ค‘๋‹จ๋˜๋ฉด React๋Š” ์บ์‹œ๋ฅผ ๋ฒ„๋ฆฝ๋‹ˆ๋‹ค. ์•ž์œผ๋กœ React๋Š” ์บ์‹œ๋ฅผ ๋ฒ„๋ฆฌ๋Š” ๊ฒƒ์„ ํ™œ์šฉํ•˜๋Š” ๋” ๋งŽ์€ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์•ž์œผ๋กœ React์— ๊ฐ€์ƒํ™”๋œ ๋ชฉ๋ก์— ๋Œ€ํ•œ ๊ธฐ๋ณธ์ ์ธ ์ง€์›์ด ์ถ”๊ฐ€๋œ๋‹ค๋ฉด ๊ฐ€์ƒํ™”๋œ ํ…Œ์ด๋ธ” ๋ทฐํฌํŠธ์—์„œ ์Šคํฌ๋กค ๋˜๋Š” ํ•ญ๋ชฉ์— ๋Œ€ํ•œ ์บ์‹œ๋ฅผ ๋ฒ„๋ฆฌ๋Š” ๊ฒƒ์ด ํ•ฉ๋ฆฌ์ ์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋Š” ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•ด useMemo์—๋งŒ ์˜์กดํ•œ๋‹ค๋ฉด ๊ดœ์ฐฎ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๋Š” ์ƒํƒœ ๋ณ€์ˆ˜๋‚˜ ref๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ์ ํ•ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค!

์ด์™€ ๊ฐ™์ด ๋ฐ˜ํ™˜๊ฐ’์„ ์บ์‹ฑํ•˜๋Š” ๊ฒƒ์„ memoization๋ผ๊ณ  ํ•˜๋ฉฐ, ์ด ํ›…์„ useMemo๋ผ๊ณ  ๋ถ€๋ฅด๋Š” ์ด์œ ์••๋‹ˆ๋‹ค.


์‚ฌ์šฉ๋ฒ•

๋น„์šฉ์ด ๋†’์€ ๋กœ์ง์˜ ์žฌ๊ณ„์‚ฐ ์ƒ๋žตํ•˜๊ธฐ

์žฌ๋ Œ๋”๋ง ์‚ฌ์ด์— ๊ณ„์‚ฐ์„ ์บ์‹ฑํ•˜๋ ค๋ฉด ์ปดํฌ๋„ŒํŠธ์˜ ์ตœ์ƒ์œ„ ๋ ˆ๋ฒจ์—์„œ useMemo๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๊ณ„์‚ฐ์„ ๊ฐ์‹ธ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

import { useMemo } from 'react';

function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
}

useMemo์— ๋‘ ๊ฐ€์ง€๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  1. () =>์™€ ๊ฐ™์ด ์ธ์ˆ˜๋ฅผ ๋ฐ›์ง€ ์•Š๊ณ  ๊ณ„์‚ฐํ•˜๋ ค๋Š” ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ณ„์‚ฐ ํ•จ์ˆ˜ ์ž…๋‹ˆ๋‹ค์€.
  2. ๊ณ„์‚ฐ ๋‚ด๋ถ€์—์„œ ์‚ฌ์šฉ๋˜๋Š” ์ปดํฌ๋„ŒํŠธ ๋‚ด์˜ ๋ชจ๋“  ๊ฐ’์„ ํฌํ•จํ•˜๋Š” ์ข…์†์„ฑ ๋ชฉ๋ก ์ž…๋‹ˆ๋‹ค.

์ดˆ๊ธฐ ๋ Œ๋”๋ง์—์„œ useMemo์—์„œ ์–ป์„ ์ˆ˜ ์žˆ๋Š” ๊ฐ’์€ ๊ณ„์‚ฐ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ ๊ฒฐ๊ณผ๊ฐ’ ์ž…๋‹ˆ๋‹ค.

์ดํ›„ ๋ชจ๋“  ๋ Œ๋”๋ง์—์„œ React๋Š” ์ข…์†์„ฑ ๋ชฉ๋ก์„ ๋งˆ์ง€๋ง‰ ๋ Œ๋”๋ง ์ค‘์— ์ „๋‹ฌํ•œ ์ข…์†์„ฑ ๋ชฉ๋ก๊ณผ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์ผ (Object.is์™€ ๋น„๊ตํ–ˆ์„ ๋•Œ) ์ข…์†์„ฑ์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด, useMemo๋Š” ์ด์ „์— ์ด๋ฏธ ๊ณ„์‚ฐํ•ด๋‘” ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š๋‹ค๋ฉด React๋Š” ๊ณ„์‚ฐ์„ ๋‹ค์‹œ ์‹คํ–‰ํ•˜๊ณ  ์ƒˆ๋กœ์šด ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

์ฆ‰, useMemo๋Š” ์ข…์†์„ฑ์ด ๋ณ€๊ฒฝ๋˜๊ธฐ ์ „๊นŒ์ง€ ์žฌ๋ Œ๋”๋ง ์‚ฌ์ด์˜ ๊ณ„์‚ฐ ๊ฒฐ๊ณผ๋ฅผ ์บ์‹ฑํ•ฉ๋‹ˆ๋‹ค.

์ด ๊ธฐ๋Šฅ์ด ์–ธ์ œ ์œ ์šฉํ•œ์ง€ ์˜ˆ์‹œ๋ฅผ ํ†ตํ•ด ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ์ ์œผ๋กœ React๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค์‹œ ๋ Œ๋”๋งํ•  ๋•Œ๋งˆ๋‹ค ์ปดํฌ๋„ŒํŠธ์˜ ์ „์ฒด ๋ณธ๋ฌธ์„ ๋‹ค์‹œ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, TodoList๊ฐ€ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ฑฐ๋‚˜ ๋ถ€๋ชจ๋กœ๋ถ€ํ„ฐ ์ƒˆ๋กœ์šด props๋ฅผ ๋ฐ›์œผ๋ฉด filterTodos ํ•จ์ˆ˜๊ฐ€ ๋‹ค์‹œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

function TodoList({ todos, tab, theme }) {
const visibleTodos = filterTodos(todos, tab);
// ...
}

์ผ๋ฐ˜์ ์œผ๋กœ ๋Œ€๋ถ€๋ถ„์˜ ๊ณ„์‚ฐ์„ ๋งค์šฐ ๋น ๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ํฐ ๋ฐฐ์—ด์„ ํ•„ํ„ฐ๋ง ํ˜น์€ ๋ณ€ํ™˜ํ•˜๊ฑฐ๋‚˜ ๋น„์šฉ์ด ๋งŽ์ด ๋“œ๋Š” ๊ณ„์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒฝ์šฐ, ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด ๊ณ„์‚ฐ์„ ์ƒ๋žตํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ todos๊ณผ tab์ด ๋งˆ์ง€๋ง‰ ๋ Œ๋”๋ง ๋•Œ์™€ ๋™์ผํ•œ ๊ฒฝ์šฐ, ์•ž์„œ ์–ธ๊ธ‰ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ useMemo๋กœ ๊ณ„์‚ฐ์„ ๊ฐ์‹ธ๋ฉด ์ด์ „์— ๊ณ„์‚ฐ๋œ visibleTodos๋ฅผ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ์œ ํ˜•์˜ ์บ์‹ฑ์„ ๋ฉ”๋ชจ์ด์ œ์ด์…˜ ๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค!

์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•ด์„œ๋งŒuseMemo๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ธฐ๋Šฅ์ด ์—†์–ด์„œ ์ฝ”๋“œ๊ฐ€ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ๊ทผ๋ณธ์ ์ธ ๋ฌธ์ œ๋ฅผ ๋จผ์ € ์ฐพ์•„์„œ ์ˆ˜์ •ํ•˜์„ธ์š”. ๊ทธ ํ›„ useMemo๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Deep Dive

๋น„์‹ผ ์—ฐ์‚ฐ์ธ์ง€ ์–ด๋–ป๊ฒŒ ์•Œ ์ˆ˜ ์žˆ๋‚˜์š”?

์ผ๋ฐ˜์ ์œผ๋กœ ์ˆ˜์ฒœ ๊ฐœ์˜ ๊ฐœ์ฒด๋ฅผ ๋งŒ๋“ค๊ฑฐ๋‚˜ ๋ฐ˜๋ณตํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด ๋น„์šฉ์ด ๋งŽ์ด ๋“ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์กฐ๊ธˆ ๋” ์ •ํ™•ํ•˜๊ฒŒ ํ™•์ธํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์ฝ˜์†” ๋กœ๊ทธ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์ฝ”๋“œ์— ์†Œ์š”๋œ ์‹œ๊ฐ„์„ ์ธก์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

console.time('filter array');
const visibleTodos = filterTodos(todos, tab);
console.timeEnd('filter array');

์ธก์ •ํ•˜๋ ค๋Š” ์ƒํ˜ธ์ž‘์šฉ(์˜ˆ์‹œ: Input์— ์ž…๋ ฅ)์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด filter array: 0.15ms์™€ ๊ฐ™์€ ๋กœ๊ทธ๊ฐ€ ์ฝ˜์†”์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ์ „์ฒด์ ์œผ๋กœ ๊ธฐ๋ก๋œ ์‹œ๊ฐ„์ด ํด ๋•Œ(์˜ˆ์‹œ: 1ms ์ด์ƒ) ํ•ด๋‹น ๊ณ„์‚ฐ์„ ๋ฉ”๋ชจํ•ด ๋‘๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ ์‹คํ—˜์ ์œผ๋กœ useMemo๋กœ ๊ณ„์‚ฐ์„ ๊ฐ์‹ธ์„œ ์ƒํ˜ธ์ž‘์šฉ์— ๋Œ€ํ•œ ์ด ์‹œ๊ฐ„์ด ๊ฐ์†Œํ–ˆ๋Š”์ง€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

console.time('filter array');
const visibleTodos = useMemo(() => {
return filterTodos(todos, tab); // todo์™€ tab์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค.
}, [todos, tab]);
console.timeEnd('filter array');

useMemo๋Š” ์ฒ˜์Œ ๋ Œ๋”๋ง์„ ๋” ๋น ๋ฅด๊ฒŒ ๋งŒ๋“ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์—…๋ฐ์ดํŠธ ์‹œ ๋ถˆํ•„์š”ํ•œ ์ž‘์—…์„ ๊ฑด๋„ˆ๋›ฐ๋Š” ๋ฐ ๋„์›€์ด ๋  ๋ฟ์ž…๋‹ˆ๋‹ค.

์ปดํ“จํ„ฐ๊ฐ€ ์‚ฌ์šฉ์ž์˜ ์ปดํ“จํ„ฐ๋ณด๋‹ค ๋น ๋ฅผ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ธ์œ„์ ์œผ๋กœ ์†๋„๋ฅผ ๋‚ฎ์ถ”์–ด ์„ฑ๋Šฅ์„ ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ๋“ค์–ด Chrome์€ CPU ์Šค๋กœํ‹€๋ง ์˜ต์…˜์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

๊ฐœ๋ฐœํ™˜๊ฒฝ์€ ๊ฐ€์žฅ ์ •ํ™•ํ•œ ๊ฒฐ๊ณผ๋ฅผ ์ œ๊ณตํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค(์˜ˆ๋ฅผ ๋“ค์–ด Strict ๋ชจ๋“œ๊ฐ€ ์ผœ์ ธ ์žˆ๋‹ค๋ฉด ๊ฐ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ•œ ๋ฒˆ์ด ์•„๋‹Œ ๋‘ ๋ฒˆ ๋ Œ๋”๋ง ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค). ๊ฐ€์žฅ ์ •ํ™•ํ•œ ํƒ€์ด๋ฐ์„ ์–ป์œผ๋ ค๋ฉด ํ”„๋กœ๋•์…˜์šฉ ์•ฑ์„ ๋นŒ๋“œํ•˜๊ณ  ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•œ ๊ธฐ๊ธฐ์—์„œ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

Deep Dive

๋ชจ๋“  ๊ณณ์— useMemo๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•˜๋‚˜์š”?

์ด ์‚ฌ์ดํŠธ์™€ ๊ฐ™์ด ๋Œ€๋ถ€๋ถ„์˜ ์ƒํ˜ธ ์ž‘์šฉ์ด ๊ฑฐ์นœ ๊ฒฝ์šฐ(ํŽ˜์ด์ง€ ๋˜๋Š” ์ „์ฒด ์„น์…˜์ด ๊ต์ฒด๋˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์ด) ๋ฉ”๋ชจ์ด์ œ์ด์…˜์ด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด, ์•ฑ์ด ๊ทธ๋ฆผ ํŽธ์ง‘๊ธฐ์™€ ๋น„์Šทํ•˜๊ณ  ๋Œ€๋ถ€๋ถ„์˜ ์ƒํ˜ธ ์ž‘์šฉ์ด ์„ธ๋ถ„ํ™”๋œ ๊ฒฝ์šฐ(๋„ํ˜• ์ด๋™๊ณผ ๊ฐ™์€) ๋ฉ”๋ชจ์ด์ œ์ด์…˜์ด ๋งค์šฐ ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

useMemo๋กœ ์ตœ์ ํ™”ํ•˜๋Š” ๊ฒƒ์€ ๋ช‡๋ช‡ ๊ฒฝ์šฐ์—๋งŒ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • useMemo์— ์ž…๋ ฅํ•˜๋Š” ๊ณ„์‚ฐ์ด ๋ˆˆ์— ๋„๊ฒŒ ๋Š๋ฆฌ๊ณ  ์ข…์†์„ฑ์ด ๊ฑฐ์˜ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ.
  • memo๋กœ ๊ฐ์‹ธ์ง„ ์ปดํฌ๋„ŒํŠธ์— prop๋กœ ์ „๋‹ฌํ•  ๊ฒฝ์šฐ. ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด ๋ Œ๋”๋ง์„ ๊ฑด๋„ˆ๋›ฐ๊ณ  ์‹ถ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ฉ”๋ชจ์ด์ œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด ์˜์กด์„ฑ์ด ๋™์ผํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋งŒ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค์‹œ ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ „๋‹ฌํ•œ ๊ฐ’์„ ๋‚˜์ค‘์— ์ผ๋ถ€ Hook์˜ ์ข…์†์„ฑ์œผ๋กœ ์ด์šฉํ•  ๊ฒฝ์šฐ. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค๋ฅธ useMemo์˜ ๊ณ„์‚ฐ ๊ฐ’์ด ์—ฌ๊ธฐ์— ์ข…์†๋˜์–ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜๋Š” useEffect์˜ ๊ฐ’์— ์ข…์†๋˜์–ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ์™ธ๋Š” ๊ณ„์‚ฐ์„ useMemo๋กœ ๊ฐ์‹ธ๋Š” ๊ฒƒ์— ๋Œ€ํ•œ ์ด๋“์ด ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๊ทธ๋ ‡๊ฒŒ ํ•œ๋‹ค๊ณ  ํ•ด์„œ ํฌ๊ฒŒ ๋ฌธ์ œ๊ฐ€ ๋˜๋Š” ๊ฒƒ๋„ ์•„๋‹ˆ๋ฏ€๋กœ ์ผ๋ถ€ ํŒ€์—์„œ๋Š” ๊ฐœ๋ณ„ ์‚ฌ๋ก€์— ๋Œ€ํ•ด ์ƒ๊ฐํ•˜์ง€ ์•Š๊ณ  ๊ฐ€๋Šฅํ•œ ํ•œ ๋งŽ์ด ๋ฉ”๋ชจํ•˜๋Š” ๋ฐฉ์‹์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. ์ด ์ ‘๊ทผ ๋ฐฉ์‹์˜ ๋‹จ์ ์€ ์ฝ”๋“œ ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์ง„๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋˜ํ•œ, ๋ชจ๋“  ๋ฉ”๋ชจ์ด์ œ์ด์…˜์ด ํšจ๊ณผ์ ์ธ ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. โ€œํ•ญ์ƒ ์ƒˆ๋กœ์šดโ€ ๋‹จ์ผ ๊ฐ’๋งŒ์œผ๋กœ๋„ ์ „์ฒด ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•œ ๋ฉ”๋ชจํ™”๊ฐ€ ๊นจ์งˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

์‹ค์ œ๋กœ ๋ช‡ ๊ฐ€์ง€ ์›์น™์„ ์ง€ํ‚ค๋ฉด ๋งŽ์€ ๋ฉ”๋ชจ์ด์ œ์ด์…˜์„ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  1. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ๊ฐ์Œ€ ๋•Œ JSX๋ฅผ ์ž์‹์ฒ˜๋Ÿผ ๋ฐ›์•„๋“ค์ด๋„๋ก ํ•˜์„ธ์š”. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฐ์‹ธ๋Š” ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ์ž์‹ ์˜ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋”๋ผ๋„ React๋Š” ์ž์‹์„ ๋‹ค์‹œ ๋ Œ๋”๋งํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
  2. ์ง€์—ญ ์ƒํƒœ๋ฅผ ์„ ํ˜ธํ•˜๊ณ  ํ•„์š” ์ด์ƒ์œผ๋กœ ์ƒํƒœ๋ฅผ ์œ„๋กœ ์˜ฌ๋ฆฌ์ง€ ๋งˆ์„ธ์š”. ์˜ˆ๋ฅผ ๋“ค์–ด, forms์™€ ๊ฐ™์ด ์ผ์‹œ์ ์ธ ์ƒํƒœ๋‚˜ ์–ด๋–ค ํ•ญ๋ชฉ์ด ํŠธ๋ฆฌ์˜ ๋งจ ์œ„์— ์œ„์น˜ํ•˜๊ฑฐ๋‚˜, ์ „์—ญ ์ƒํƒœ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์žˆ๊ฒŒ ํ•˜์ง€ ๋งˆ์„ธ์š”.
  3. ์ˆœ์ˆ˜ํ•œ ๋ Œ๋”๋ง ๋กœ์ง์„ ์œ ์ง€ํ•˜์„ธ์š”. ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค์‹œ ๋ Œ๋”๋งํ•  ๋•Œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฑฐ๋‚˜ ๋ˆˆ์— ๋„๋Š” ์‹œ๊ฐ์ ์ธ ๋ถ€์ž‘์šฉ์ด ๋ฐœ์ƒํ•˜๋ฉด ์ปดํฌ๋„ŒํŠธ์— ๋ฒ„๊ทธ๊ฐ€ ์žˆ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค! ๋ฉ”๋ชจ์ด์ œ์ด์…˜์„ ํ•˜๋Š” ๋Œ€์‹  ๋ฒ„๊ทธ๋ฅผ ์ˆ˜์ •ํ•˜์„ธ์š”.
  4. ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ถˆํ•„์š”ํ•œ Effect๋ฅผ ํ”ผํ•˜์„ธ์š”. React ์•ฑ์˜ ๋Œ€๋ถ€๋ถ„์˜ ์„ฑ๋Šฅ ๋ฌธ์ œ๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฐ˜๋ณต์ ์œผ๋กœ ๋ Œ๋”๋งํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” Effect์˜ ์—…๋ฐ์ดํŠธ ์ฒด์ธ์œผ๋กœ๋ถ€ํ„ฐ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
  5. Effects์—์„œ ๋ถˆํ•„์š”ํ•œ ์ข…์†์„ฑ์„ ์ œ๊ฑฐํ•˜์„ธ์š”. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฉ”๋ชจ์ด์ œ์ด์…˜์„ ํ•˜๋Š” ๋Œ€์‹  ์ผ๋ถ€ ๊ฐ์ฒด๋‚˜ ํ•จ์ˆ˜๋ฅผ Effect ๋‚ด๋ถ€ ๋˜๋Š” ์ปดํฌ๋„ŒํŠธ ์™ธ๋ถ€๋กœ ์ด๋™ํ•˜๋Š” ๊ฒƒ์ด ๋” ๊ฐ„๋‹จํ•  ๋•Œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

ํŠน์ • ์ƒํ˜ธ์ž‘์šฉ์ด ์—ฌ์ „ํžˆ ๋Š๋ฆฌ๊ฒŒ ๋Š๊ปด์ง„๋‹ค๋ฉด React ๊ฐœ๋ฐœ์ž ๋„๊ตฌ ํ”„๋กœํŒŒ์ผ๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฉ”๋ชจ์ด์ œ์ด์…˜์„ ํ†ตํ•ด ๊ฐ€์žฅ ํฐ ์ด์ ์„ ์–ป์„ ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ํ•„์š”ํ•˜๋‹ค๋ฉด ์ถ”๊ฐ€ํ•˜์„ธ์š”. ์ด๋Ÿฌํ•œ ์›์น™์€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋” ์‰ฝ๊ฒŒ ๋””๋ฒ„๊น…ํ•˜๊ณ  ์ดํ•ดํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋ฏ€๋กœ ์–ด๋–ค ๊ฒฝ์šฐ๋“  ์ด ์›์น™์„ ๋”ฐ๋ฅด๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ์žฅ๊ธฐ์ ์œผ๋กœ ์šฐ๋ฆฌ๋Š” ์ด ๋ฌธ์ œ๋ฅผ ์™„์ „ํžˆ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์ž๋™์  ์„ธ๋ถ„ํ™” ๋ฉ”๋ชจ์ด์ œ์ด์…˜์„ ์—ฐ๊ตฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

useMemo์™€ ๊ฐ’์„ ์ง์ ‘ ๊ณ„์‚ฐํ•˜๋Š” ๊ฒƒ์˜ ์ฐจ์ด์ 

์˜ˆ์ œ 1 of 2:
useMemo๋กœ ์žฌ๊ณ„์‚ฐ ๊ฑด๋„ˆ๋›ฐ๊ธฐ

์ด ์˜ˆ์ œ์—์„œ๋Š” ๋ Œ๋”๋ง ์ค‘์— ํ˜ธ์ถœํ•˜๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํ•จ์ˆ˜๊ฐ€ ์‹ค์ œ๋กœ ๋Š๋ฆด ๋•Œ ์–ด๋–ค ์ผ์ด ๋ฐœ์ƒํ•˜๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋„๋ก filterTodos์„ ์ธ์œ„์ ์œผ๋กœ ๋Š๋ฆฌ๊ฒŒ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ํƒญ์„ ์ „ํ™˜ํ•˜๊ณ  ํ…Œ๋งˆ๋ฅผ ํ† ๊ธ€ํ•ด ๋ณด์„ธ์š”.

ํƒญ์„ ์ „ํ™˜ํ•˜๋ฉด ๋Š๋ ค์ง„ filterTodos๊ฐ€ ๋‹ค์‹œ ์‹คํ–‰๋˜๋ฏ€๋กœ ๋Š๋ฆฌ๊ฒŒ ๋Š๊ปด์ง‘๋‹ˆ๋‹ค. ์ด๋Š” tab์ด ๋ณ€๊ฒฝ๋˜์—ˆ์œผ๋ฏ€๋กœ ์ „์ฒด ๊ณ„์‚ฐ์ด ํ•„์ˆ˜์ ์œผ๋กœ ๋‹ค์‹œ ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋‚˜ํƒ€๋‚˜๋Š” ํ˜„์ƒ์ž…๋‹ˆ๋‹ค. (์™œ ๋‘ ๋ฒˆ ์‹คํ–‰๋˜๋Š”์ง€ ๊ถ๊ธˆํ•˜๋‹ค๋ฉด ์—ฌ๊ธฐ๋ฅผ ํด๋ฆญํ•ด์„œ ์„ค๋ช…์„ ํ™•์ธํ•˜์„ธ์š”.)

ํ…Œ๋งˆ๋ฅผ ์ „ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ธ์œ„์ ์ธ ์†๋„ ์ €ํ•˜์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ๋น ๋ฅธ ์ด์œ ๋Š” useMemo ๋•๋ถ„์ž…๋‹ˆ๋‹ค! ๋Š๋ฆฐ ์†๋„์˜ filterTodos๋Š” ๋งˆ์ง€๋ง‰ ๋ Œ๋”๋ง ์ดํ›„ (useMemo์— ์ข…์†์„ฑ์œผ๋กœ ์ „๋‹ฌํ•œ)todos์™€ tab์ด ๋ชจ๋‘ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— ํ˜ธ์ถœ์„ ๊ฑด๋„ˆ๋›ฐ์—ˆ์Šต๋‹ˆ๋‹ค.

import { useMemo } from 'react';
import { filterTodos } from './utils.js'

export default function TodoList({ todos, theme, tab }) {
  const visibleTodos = useMemo(
    () => filterTodos(todos, tab),
    [todos, tab]
  );
  return (
    <div className={theme}>
      <p><b>Note: <code>filterTodos</code> is artificially slowed down!</b></p>
      <ul>
        {visibleTodos.map(todo => (
          <li key={todo.id}>
            {todo.completed ?
              <s>{todo.text}</s> :
              todo.text
            }
          </li>
        ))}
      </ul>
    </div>
  );
}


์ปดํฌ๋„ŒํŠธ ์žฌ๋ Œ๋”๋ง ๊ฑด๋„ˆ๋›ฐ๊ธฐ

๊ฒฝ์šฐ์— ๋”ฐ๋ผ useMemo๋Š” ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ ์žฌ๋ Œ๋”๋ง ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•˜๋Š”๋ฐ ๋„์›€์ด ๋  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์„ค๋ช…ํ•˜๊ธฐ ์œ„ํ•ด TodoList ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ž์‹ ์ปดํฌ๋„ŒํŠธ์ธ List์— visibleTodos๋ฅผ prop๋กœ ์ „๋‹ฌํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

export default function TodoList({ todos, tab, theme }) {
// ...
return (
<div className={theme}>
<List items={visibleTodos} />
</div>
);
}

theme prop๋ฅผ ํ† ๊ธ€ํ•˜๋ฉด ์•ฑ์ด ์ž ์‹œ ๋ฉˆ์ถ”๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ JSX์—์„œ <List />๋ฅผ ์ œ๊ฑฐํ•˜๋ฉด ๋น ๋ฅด๊ฒŒ ๋Š๊ปด์ง‘๋‹ˆ๋‹ค. ์ด๋Š” List ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ตœ์ ํ™”ํ•  ๊ฐ€์น˜๊ฐ€ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๋ ค์ค๋‹ˆ๋‹ค.

๊ธฐ๋ณธ์ ์œผ๋กœ React๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‹ค์‹œ ๋ Œ๋”๋ง ๋  ๋•Œ, ๋ชจ๋“  ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์žฌ๊ท€์ ์œผ๋กœ ๋‹ค์‹œ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฏ€๋กœ TodoList๊ฐ€ ๋‹ค๋ฅธ theme๋กœ ๋‹ค์‹œ ๋ Œ๋”๋ง ๋˜๋ฉด List ์ปดํฌ๋„ŒํŠธ ๋˜ํ•œ ๋‹ค์‹œ ๋ Œ๋”๋ง ๋ฉ๋‹ˆ๋‹ค. ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜๋Š” ๋ฐ ๋งŽ์€ ๊ณ„์‚ฐ์ด ํ•„์š”ํ•˜์ง€ ์•Š๋Š” ์ปดํฌ๋„ŒํŠธ๋Š” ๊ดœ์ฐฎ์ง€๋งŒ, ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜๋Š” ๊ฒƒ์ด ๋Š๋ฆฌ๋‹ค๋Š” ๊ฒƒ์„ ํ™•์ธํ–ˆ๋‹ค๋ฉด List๋ฅผ memo๋ฅผ ํ†ตํ•ด ๊ฐ์‹ธ์„œ props๊ฐ€ ๋งˆ์ง€๋ง‰ ๋ Œ๋”๋ง ์‹œ์ ๊ณผ ๋™์ผ ํ•  ๋•Œ ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜๋Š” ๊ฒƒ์„ ์ƒ๋žตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { memo } from 'react';

const List = memo(function List({ items }) {
// ...
});

์ด ๋ณ€๊ฒฝ์œผ๋กœ List๋Š” ๋ชจ๋“  props๊ฐ€ ๋งˆ์ง€๋ง‰ ๋ Œ๋”๋ง ๋•Œ์™€ ๋™์ผํ•œ ๊ฒฝ์šฐ ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ๊ณ„์‚ฐ์„ ์บ์‹ฑํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค! useMemo์—†์ด visibleTodos๋ฅผ ๊ณ„์‚ฐํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ด…์‹œ๋‹ค.

export default function TodoList({ todos, tab, theme }) {
// ํ…Œ๋งˆ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ๋งˆ๋‹ค ๋‹ค๋ฅธ ๋ฐฐ์—ด์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.
const visibleTodos = filterTodos(todos, tab);
return (
<div className={theme}>
{/* ... List์˜ props๋Š” ๋™์ผํ•˜์ง€ ์•Š์œผ๋ฉฐ ๋งค๋ฒˆ ๋‹ค์‹œ ๋ Œ๋”๋ง ๋ฉ๋‹ˆ๋‹ค. */}
<List items={visibleTodos} />
</div>
);
}

์œ„์˜ ์˜ˆ์‹œ์—์„œ filterTodos ํ•จ์ˆ˜๋Š” ํ•ญ์ƒ ๋‹ค๋ฅธ ๋ฐฐ์—ด์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” {} ๊ฐ์ฒด ๋ฆฌํ„ฐ๋Ÿด์ด ํ•ญ์ƒ ์ƒˆ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ๊ณผ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ์ด๋Š” ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š์ง€๋งŒ List์˜ props๋Š” ๋™์ผํ•˜์ง€ ์•Š์œผ๋ฉฐ memo๋ฅผ ์‚ฌ์šฉํ•œ ์ตœ์ ํ™”๊ฐ€ ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ useMemo๊ฐ€ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

export default function TodoList({ todos, tab, theme }) {
// ์žฌ๋ Œ๋”๋ง ์‚ฌ์ด์— ๊ณ„์‚ฐ์„ ์บ์‹ฑํ•˜๋„๋ก React์— ์ง€์‹œํ•ฉ๋‹ˆ๋‹ค...
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab] // ...๋”ฐ๋ผ์„œ ํ•ด๋‹น ์ข…์†์„ฑ์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š” ํ•œ...
);
return (
<div className={theme}>
{/* ...List์— ๋™์ผํ•œ props๊ฐ€ ์ „๋‹ฌ๋˜์–ด ์žฌ๋ Œ๋”๋ง์„ ์ƒ๋žตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. */}
<List items={visibleTodos} />
</div>
);
}

visibleTodos์—ฐ์‚ฐ์„ useMemo๋กœ ๊ฐ์‹ธ๋ฉด ๋‹ค์‹œ ๋ Œ๋”๋ง ๋  ๋•Œ๋งˆ๋‹ค ๊ฐ™์€ ๊ฐ’์„ ๊ฐ–๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (์ข…์†์„ฑ์ด ๋ณ€๊ฒฝ๋˜๊ธฐ ์ „๊นŒ์ง€). ํŠน๋ณ„ํ•œ ์ด์œ ๊ฐ€ ์—†๋Š” ํ•œ ์—ฐ์‚ฐ์„ useMemo๋กœ ๊ฐ์‹ธ์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค. ์ด ์˜ˆ์ œ์—์„œ๋Š” memo๋กœ ๊ฐ์‹ธ์ง„ ์ปดํฌ๋„ŒํŠธ์— ์ „๋‹ฌํ•˜๋ฉด ์žฌ๋ Œ๋”๋ง์„ ๊ฑด๋„ˆ๋›ธ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด ํŽ˜์ด์ง€์—์„œ ์ž์„ธํžˆ ์„ค๋ช…ํ•˜๋Š” useMemo๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•˜๋Š” ๋ช‡ ๊ฐ€์ง€ ๋‹ค๋ฅธ ์ด์œ ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

Deep Dive

๊ฐœ๋ณ„ JSX ๋…ธ๋“œ ๋ฉ”๋ชจํ™”

List๋ฅผ memo๋กœ ๊ฐ์‹ธ๋Š” ๋Œ€์‹ , <List /> ๋…ธ๋“œ ์ž์ฒด๋ฅผ useMemo๋กœ ๊ฐ์‹ธ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

export default function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
const children = useMemo(() => <List items={visibleTodos} />, [visibleTodos]);
return (
<div className={theme}>
{children}
</div>
);
}

๋™์ž‘ ๋ฐฉ์‹์€ ๋™์ผํ•ฉ๋‹ˆ๋‹ค. visibleTodos์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ List๋Š” ๋‹ค์‹œ ๋ Œ๋”๋ง ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

<List items={visibleTodos} />์™€ ๊ฐ™์€ JSX ๋…ธ๋“œ๋Š” { type: List, props: { items: visibleTodos } }์™€ ๊ฐ™์€ ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. ์ด ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ์ €๋ ดํ•˜์ง€๋งŒ, React๋Š” ๊ทธ ๋‚ด์šฉ์ด ์ง€๋‚œ๋ฒˆ๊ณผ ๋™์ผํ•œ์ง€ ์•Œ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ React๋Š” List ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค์‹œ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ React๊ฐ€ ์ด์ „ ๋ Œ๋”๋ง๊ณผ ๋™์ผํ•œ JSX๋ฅผ ๋ฐœ๊ฒฌํ•˜๋ฉด ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜๋ ค๊ณ  ์‹œ๋„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. JSX ๋…ธ๋“œ๋Š” ๋ถˆ๋ณ€ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. JSX ๋…ธ๋“œ ๊ฐ์ฒด๋Š” ์‹œ๊ฐ„์ด ์ง€๋‚˜๋„ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ React๋Š” ์žฌ๋ Œ๋”๋ง์„ ์ƒ๋žตํ•ด๋„ ์•ˆ์ „ํ•˜๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๊ฒƒ์ด ๋™์ž‘ํ•˜๋ ค๋ฉด ๋…ธ๋“œ๊ฐ€ ๋‹จ์ˆœํžˆ ์ฝ”๋“œ์ ์œผ๋กœ ๋™์ผํ•ด ๋ณด์ด๋Š” ๊ฒƒ์ด ์•„๋‹Œ ์‹ค์ œ๋กœ ๋™์ผํ•œ ๊ฐ์ฒด์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ์˜ˆ์ œ์—์„œ๋Š” useMemo๊ฐ€ ํ•ด๋‹น ์ผ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

JSX ๋…ธ๋“œ๋ฅผ useMemo๋กœ ์ˆ˜๋™์œผ๋กœ ๊ฐ์‹ธ๋Š” ๊ฒƒ์€ ํŽธ๋ฆฌํ•œ ๋ฐฉ๋ฒ•์€ ์•„๋‹™๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์กฐ๊ฑด๋ถ€๋กœ๋Š” ์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋ณดํ†ต JSX ๋…ธ๋“œ๋ฅผ ๊ฐ์‹ธ๋Š” ๋Œ€์‹  ์ปดํฌ๋„ŒํŠธ๋ฅผ memo๋กœ ๊ฐ์Œ‰๋‹ˆ๋‹ค.

์žฌ๋ Œ๋”๋ง์„ ๊ฑด๋„ˆ๋›ฐ๋Š” ๊ฒƒ๊ณผ ํ•ญ์ƒ ์žฌ๋ Œ๋”๋ง์„ ํ•˜๋Š” ๊ฒƒ์˜ ์ฐจ์ด์ 

์˜ˆ์ œ 1 of 2:
useMemo ๋ฐ memo๋กœ ์žฌ๋ Œ๋”๋ง ๊ฑด๋„ˆ๋›ฐ๊ธฐ

์ด ์˜ˆ์ œ์—์„œ๋Š” List ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ธ์œ„์ ์œผ๋กœ ๋Š๋ฆฌ๊ฒŒ ๋งŒ๋“ค์–ด ๋ Œ๋”๋ง ์ค‘์ธ React ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์‹ค์ œ๋กœ ๋Š๋ ค์งˆ ๋•Œ ์–ด๋–ค ์ผ์ด ๋ฐœ์ƒํ•˜๋Š” ์ง€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํƒญ์„ ์ „ํ™˜ํ•˜๊ณ  ํ…Œ๋งˆ๋ฅผ ํ† ๊ธ€ํ•ด ๋ณด์„ธ์š”.

ํƒญ์„ ์ „ํ™˜ํ•˜๋ฉด ๋Š๋ ค์ง„ List๊ฐ€ ๋‹ค์‹œ ๋ Œ๋”๋ง ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋Š๋ฆฌ๊ฒŒ ๋Š๊ปด์ง‘๋‹ˆ๋‹ค. ์ด๋Š” tab์ด ๋ณ€๊ฒฝ๋˜์—ˆ์œผ๋ฏ€๋กœ ์‚ฌ์šฉ์ž์˜ ์ƒˆ๋กœ์šด ์„ ํƒ ์‚ฌํ•ญ์„ ํ™”๋ฉด์— ๋ฐ˜์˜ํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์˜ˆ์ƒ๋˜๋Š” ํ˜„์ƒ์ž…๋‹ˆ๋‹ค.

๋‹ค์Œ์œผ๋กœ ํ…Œ๋งˆ๋ฅผ ํ† ๊ธ€ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ธ์œ„์ ์ธ ์†๋„ ์ €ํ•˜์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  memo์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ๋œ useMemo ๋•๋ถ„์— ๋น ๋ฆ…๋‹ˆ๋‹ค! List๋Š” ๋งˆ์ง€๋ง‰ ๋ Œ๋”๋ง ์ดํ›„ visibleItems ๋ฐฐ์—ด์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— ์žฌ๋ Œ๋”๋ง์„ ์ƒ๋žตํ–ˆ์Šต๋‹ˆ๋‹ค. (useMemo์— ์ข…์†์„ฑ์œผ๋กœ ์ „๋‹ฌ๋œ) todos์™€ tab์ด ๋ชจ๋‘ ๋งˆ์ง€๋ง‰ ๋ Œ๋”๋ง ์ดํ›„ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์œผ๋ฏ€๋กœ visibleItems ๋ฐฐ์—ด์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

import { useMemo } from 'react';
import List from './List.js';
import { filterTodos } from './utils.js'

export default function TodoList({ todos, theme, tab }) {
  const visibleTodos = useMemo(
    () => filterTodos(todos, tab),
    [todos, tab]
  );
  return (
    <div className={theme}>
      <p><b>Note: <code>List</code> is artificially slowed down!</b></p>
      <List items={visibleTodos} />
    </div>
  );
}


๋‹ค๋ฅธ Hook์˜ ์ข…์†์„ฑ ๋ฉ”๋ชจํ™”

์ปดํฌ๋„ŒํŠธ ๋ณธ๋ฌธ์—์„œ ์ง์ ‘ ์ƒ์„ฑ๋œ ๊ฐ์ฒด์— ์˜์กดํ•˜๋Š” ์—ฐ์‚ฐ์ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

function Dropdown({ allItems, text }) {
const searchOptions = { matchMode: 'whole-word', text };

const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // ๐Ÿšฉ ์ฃผ์˜: ์ปดํฌ๋„ŒํŠธ ๋ณธ๋ฌธ์—์„œ ์ƒ์„ฑ๋œ ๊ฐ์ฒด์— ๋Œ€ํ•œ ์ข…์†์„ฑ
// ...

์ด๋ ‡๊ฒŒ ๊ฐ์ฒด์— ์˜์กดํ•˜๋Š” ๊ฒƒ์€ ๋ฉ”๋ชจ์ด์ œ์ด์…˜์˜ ๋ชฉ์ ์„ ๋ฌด์ƒ‰ํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‹ค์‹œ ๋ Œ๋”๋ง ๋˜๋ฉด ์ปดํฌ๋„ŒํŠธ ๋ณธ๋ฌธ ๋‚ด๋ถ€์˜ ๋ชจ๋“  ์ฝ”๋“œ๊ฐ€ ๋‹ค์‹œ ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. searchOptions ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์ฝ”๋“œ๋„ ๋‹ค์‹œ ๋ Œ๋”๋ง ๋  ๋•Œ๋งˆ๋‹ค ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. searchOptions์€ useMemo ํ˜ธ์ถœ์˜ ์ข…์†์„ฑ์ด๊ณ  ๋งค๋ฒˆ ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์—, React๋Š” ์ข…์†์„ฑ์ด ๋‹ค๋ฅธ ๊ฒƒ์„ ์•Œ๊ณ searchItems์„ ๋งค๋ฒˆ ๋‹ค์‹œ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.

์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด searchOptions ๊ฐ์ฒด ์ž์ฒด๋ฅผ ์ข…์†์„ฑ์œผ๋กœ ์ „๋‹ฌํ•˜๊ธฐ ์ „์— ๋ฉ”๋ชจํ•ด๋‘๋ฉด ๋ฉ๋‹ˆ๋‹ค.

function Dropdown({ allItems, text }) {
const searchOptions = useMemo(() => {
return { matchMode: 'whole-word', text };
}, [text]); // โœ… text๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งŒ ๋ณ€๊ฒฝ

const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // โœ… allItems์ด๋‚˜ searchOptions์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งŒ ๋ณ€๊ฒฝ
// ...

์œ„์˜ ์˜ˆ์ œ์—์„œ text๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด searchOptions ๊ฐ์ฒด๋„ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๋ณด๋‹ค ๋” ๋‚˜์€ ๋ฐฉ๋ฒ•์€ searchOptions๋ฅผ useMemo ๊ณ„์‚ฐ ํ•จ์ˆ˜์˜ ๋‚ด๋ถ€์— ์„ ์–ธํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

function Dropdown({ allItems, text }) {
const visibleItems = useMemo(() => {
const searchOptions = { matchMode: 'whole-word', text };
return searchItems(allItems, searchOptions);
}, [allItems, text]); // โœ… allItems์ด๋‚˜ text๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งŒ ๋ณ€๊ฒฝ
// ...

์ด์ œ ์—ฐ์‚ฐ์€ text ์— ์ง์ ‘์ ์œผ๋กœ ์˜์กดํ•ฉ๋‹ˆ๋‹ค (๋ฌธ์ž์—ด์ด๋ฏ€๋กœ โ€œ์‹ค์ˆ˜๋กœโ€ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์—†์Œ).


ํ•จ์ˆ˜ ๋ฉ”๋ชจํ™”

Form ์ปดํฌ๋„ŒํŠธ๊ฐ€ memo๋กœ ๊ฐ์‹ธ์ ธ ์žˆ๊ณ  ์—ฌ๊ธฐ์— prop๋กœ ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•˜๊ณ  ์‹ถ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ด…์‹œ๋‹ค.

export default function ProductPage({ productId, referrer }) {
function handleSubmit(orderDetails) {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
}

return <Form onSubmit={handleSubmit} />;
}

{}๊ฐ€ ๋‹ค๋ฅธ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ function() {}์™€ ๊ฐ™์€ ํ•จ์ˆ˜ ์„ ์–ธ๊ณผ () => {} ๊ฐ™์€ ํ‘œํ˜„์‹์€ ๋‹ค์‹œ ๋ Œ๋”๋ง ๋  ๋•Œ๋งˆ๋‹ค ๋‹ค๋ฅธ ํ•จ์ˆ˜๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ ์ž์ฒด๋Š” ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š์œผ๋ฉฐ ํ”ผํ•ด์•ผ ํ•  ์ผ์ด ์•„๋‹™๋‹ˆ๋‹ค! ๊ทธ๋Ÿฌ๋‚˜ Form ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฉ”๋ชจํ™”๋˜์–ด ์žˆ๋‹ค๋ฉด props๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜์„ ๋•Œ ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜๋Š” ๊ฒƒ์„ ์ƒ๋žตํ•˜๊ณ  ์‹ถ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ•ญ์ƒ ๋‹ค๋ฅธ prop์€ ๋ฉ”๋ชจ์ด์ œ์ด์…˜์˜ ๋ชฉ์ ์„ ๋ฌด์ƒ‰ํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

useMemo๋กœ ํ•จ์ˆ˜๋ฅผ ๋ฉ”๋ชจํ•˜๋ ค๋ฉด ๊ณ„์‚ฐ ํ•จ์ˆ˜์—์„œ ๋‹ค๋ฅธ ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

export default function Page({ productId, referrer }) {
const handleSubmit = useMemo(() => {
return (orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
};
}, [productId, referrer]);

return <Form onSubmit={handleSubmit} />;
}

์œ„ ์˜ˆ์ œ๋Š” ํˆฌ๋ฐ•ํ•ด ๋ณด์ž…๋‹ˆ๋‹ค! ํ•จ์ˆ˜๋ฅผ ๋ฉ”๋ชจํ•˜๋Š” ๊ฒƒ์€ ์ถฉ๋ถ„ํžˆ ์ผ๋ฐ˜์ ์ด๊ธฐ ๋•Œ๋ฌธ์— React์—๋Š” ์ด๋ฅผ ์œ„ํ•œ Hook์ด ๋‚ด์žฅ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. useMemo ๋Œ€์‹  useCallback์œผ๋กœ ํ•จ์ˆ˜๋ฅผ ๊ฐ์‹ธ์„œ ์ค‘์ฒฉ๋œ ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€๋กœ ์ž‘์„ฑํ•˜์ง€ ์•Š๋„๋ก ํ•˜์„ธ์š”.

export default function Page({ productId, referrer }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
}, [productId, referrer]);

return <Form onSubmit={handleSubmit} />;
}

์œ„ ๋‘ ์˜ˆ์ œ๋Š” ์™„์ „ํžˆ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. useCallback์˜ ์œ ์ผํ•œ ์žฅ์ ์€ ๋‚ด๋ถ€์— ์ค‘์ฒฉ๋œ ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€๋กœ ์ž‘์„ฑํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ ์™ธ์—๋Š” ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. useCallback์— ๋Œ€ํ•ด ๋” ์ฝ์–ด๋ณด์„ธ์š”.


๋ฌธ์ œ ํ•ด๊ฒฐํ•˜๊ธฐ

๋ Œ๋”๋งํ•  ๋•Œ๋งˆ๋‹ค ๊ณ„์‚ฐ์ด ๋‘ ๋ฒˆ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค

Strict ๋ชจ๋“œ์—์„œ React๋Š” ์ผ๋ถ€ ํ•จ์ˆ˜๋ฅผ ํ•œ ๋ฒˆ์ด ์•„๋‹Œ ๋‘ ๋ฒˆ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

function TodoList({ todos, tab }) {
// ์ด ์ปดํฌ๋„ŒํŠธ ํ•จ์ˆ˜๋Š” ๋ Œ๋”๋งํ•  ๋•Œ๋งˆ๋‹ค ๋‘ ๋ฒˆ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

const visibleTodos = useMemo(() => {
// ์ข…์†์„ฑ ์ค‘ ํ•˜๋‚˜๋ผ๋„ ๋ณ€๊ฒฝ๋˜๋ฉด ์ด ๊ณ„์‚ฐ์€ ๋‘ ๋ฒˆ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
return filterTodos(todos, tab);
}, [todos, tab]);

// ...

์ด๋Š” ์˜ˆ์ƒ๋˜๋Š” ํ˜„์ƒ์ด๋ฉฐ ์ฝ”๋“œ๋ฅผ ์†์ƒ์‹œํ‚ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ด ๊ฐœ๋ฐœ ์ „์šฉ ๋™์ž‘์€ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ˆœ์ˆ˜ํ•˜๊ฒŒ ์œ ์ง€๋  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค. React๋Š” ํ˜ธ์ถœ ๊ฒฐ๊ณผ ์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ๋‹ค๋ฅธ ํ˜ธ์ถœ ๊ฒฐ๊ณผ๋Š” ๋ฌด์‹œํ•ฉ๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ์™€ ๊ณ„์‚ฐ ํ•จ์ˆ˜๊ฐ€ ์ˆœ์ˆ˜ํ•˜๋‹ค๋ฉด ๋กœ์ง์— ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์‹ค์ˆ˜๋กœ ๋ฐœ์ƒํ•˜๋Š” ๋ถˆ์ˆœํ•œ ๊ฒฝ์šฐ์— ๋ฐœ์ƒํ•˜๋Š” ์‹ค์ˆ˜๋ฅผ ๋ฐœ๊ฒฌํ•˜๊ณ  ์ˆ˜์ •ํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ค๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ์•„๋ž˜์˜ ๋ถˆ์ˆœํ•œ ๊ณ„์‚ฐ ํ•จ์ˆ˜๋Š” prop์œผ๋กœ ๋ฐ›์€ ๋ฐฐ์—ด์„ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.

const visibleTodos = useMemo(() => {
// ๐Ÿšฉ Mistake: mutating a prop
todos.push({ id: 'last', text: 'Go for a walk!' });
const filtered = filterTodos(todos, tab);
return filtered;
}, [todos, tab]);

React๊ฐ€ ํ•จ์ˆ˜๋ฅผ ๋‘ ๋ฒˆ ํ˜ธ์ถœํ•˜๋ฏ€๋กœ todo๊ฐ€ ๋‘ ๋ฒˆ ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. ๊ณ„์‚ฐ์ด ๊ธฐ์กด์˜ ๊ฐ์ฒด๋ฅผ ๋ณ€๊ฒฝํ•ด์„œ๋Š” ์•ˆ ๋˜์ง€๋งŒ ๊ณ„์‚ฐ ์ค‘์— ์ƒ์„ฑ๋œ ์ƒˆ๋กœ์šด ๊ฐ์ฒด๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์€ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด filterTodos ํ•จ์ˆ˜๊ฐ€ ํ•ญ์ƒ ๋‹ค๋ฅธ ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ ๋Œ€์‹  ํ•ด๋‹น ๋ฐฐ์—ด์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

const visibleTodos = useMemo(() => {
const filtered = filterTodos(todos, tab);
// โœ… ์ •๋‹ต: ๊ณ„์‚ฐ ์ค‘์— ์ƒ์„ฑํ•œ ๊ฐ์ฒด๋ฅผ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
filtered.push({ id: 'last', text: 'Go for a walk!' });
return filtered;
}, [todos, tab]);

์ˆœ์ˆ˜์„ฑ์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด๋ ค๋ฉด ์ปดํฌ๋„ŒํŠธ ์ˆœ์ˆ˜ํ•˜๊ฒŒ ์œ ์ง€ํ•˜๊ธฐ๋ฅผ ์ฝ์–ด๋ณด์„ธ์š”.

๋˜ํ•œ ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์—†๋Š” ๊ฐ์ฒด ์—…๋ฐ์ดํŠธ ๋ฐ ๋ฐฐ์—ด ์—…๋ฐ์ดํŠธ์— ๋Œ€ํ•œ ๊ฐ€์ด๋“œ๋„ ํ™•์ธํ•ด๋ณด์„ธ์š”.


useMemo๊ฐ€ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ด์•ผ ํ•˜๋Š”๋ฐ undefined๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

์ด ์ฝ”๋“œ๋Š” ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

// ๐Ÿ”ด () => { ์™€ ๊ฐ™์€ ํ™”์‚ดํ‘œ ํ•จ์ˆ˜๋Š” ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
const searchOptions = useMemo(() => {
matchMode: 'whole-word',
text: text
}, [text]);

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์˜ () => {๋Š” ํ™”์‚ดํ‘œ ํ•จ์ˆ˜์˜ ๋ณธ๋ฌธ์˜ ์‹œ์ž‘์ด๋ฏ€๋กœ { ์ค‘๊ด„ํ˜ธ๋Š” ๊ฐ์ฒด์˜ ์ผ๋ถ€๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๊ณ  ์‹ค์ˆ˜ํ•˜๋Š” ์ง€์ ์ž…๋‹ˆ๋‹ค. ({ ๊ณผ }) ๊ฐ™์€ ๊ด„ํ˜ธ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// T์ด๊ฒƒ์€ ์ž‘๋™ํ•˜์ง€๋งŒ ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ๋‹ค์‹œ ์œ„๋ฐ˜ํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค.
const searchOptions = useMemo(() => ({
matchMode: 'whole-word',
text: text
}), [text]);

ํ•˜์ง€๋งŒ ํ•ด๋‹น ๋ฐฉ์‹์€ ์—ฌ์ „ํžˆ ํ˜ผ๋ž€์„ ์ฃผ๊ณ , ๊ด„ํ˜ธ๋ฅผ ์ œ๊ฑฐํ•˜๋ฉด์„œ ๋ˆ„๊ตฐ๊ฐ€ ์‰ฝ๊ฒŒ ์œ„๋ฐ˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ์‹ค์ˆ˜๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด return ๋ฌธ์„ ๋ช…์‹œ์ ์œผ๋กœ ์ž‘์„ฑํ•˜์„ธ์š”.

// โœ… ์ด๊ฒƒ์€ ์ž‘๋™ํ•˜๋ฉฐ ๋ช…ํ™•ํ•ฉ๋‹ˆ๋‹ค.
const searchOptions = useMemo(() => {
return {
matchMode: 'whole-word',
text: text
};
}, [text]);

์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง ๋  ๋•Œ๋งˆ๋‹ค useMemo์˜ ๊ณ„์‚ฐ์ด ๋‹ค์‹œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์ข…์†์„ฑ ๋ฐฐ์—ด์„ ์ง€์ •ํ–ˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”!

์ข…์†์„ฑ ๋ฐฐ์—ด์„ ์ง€์ •ํ•˜์ง€ ์•Š์•˜์„ ๊ฒฝ์šฐ useMemo๋Š” ๋งค๋ฒˆ ๋‹ค์‹œ ๊ณ„์‚ฐ์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

function TodoList({ todos, tab }) {
// ๐Ÿ”ด ์ข…์†์„ฑ ๋ฐฐ์—ด์ด ์—†์–ด ๋งค๋ฒˆ ์žฌ๊ณ„์‚ฐ ๋จ.
const visibleTodos = useMemo(() => filterTodos(todos, tab));
// ...

์ด๊ฒƒ์€ ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์ข…์†์„ฑ ๋ฐฐ์—ด์„ ์ „๋‹ฌํ•˜๋Š” ์ˆ˜์ •๋œ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.

function TodoList({ todos, tab }) {
// โœ… ๋ถˆํ•„์š”ํ•œ ์žฌ๊ณ„์‚ฐ์„ ํ•˜์ง€ ์•Š์Œ.
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...

๋งŒ์ผ ์œ„์˜ ์˜ˆ์ œ๊ฐ€ ๋„์›€์ด ๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด, ์ข…์†์„ฑ ์ค‘ ํ•˜๋‚˜ ์ด์ƒ์ด ์ด์ „ ๋ Œ๋”๋ง๊ณผ ๋‹ฌ๋ผ์กŒ๋‹ค๋Š” ๋ฌธ์ œ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ข…์†์„ฑ ๋“ค์„ ์ฝ˜์†”์— ์ˆ˜๋™์œผ๋กœ ๋กœ๊น…ํ•˜์—ฌ ์ด ๋ฌธ์ œ๋ฅผ ๋””๋ฒ„๊ทธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
console.log([todos, tab]);

๊ทธ๋Ÿฐ ๋‹ค์Œ ์ฝ˜์†”์—์„œ ์„œ๋กœ ๋‹ค๋ฅธ ๋ฆฌ๋ Œ๋”์˜ ๋ฐฐ์—ด์„ ๋งˆ์šฐ์Šค ์˜ค๋ฅธ์ชฝ ๋ฒ„ํŠผ์œผ๋กœ ํด๋ฆญํ•˜๊ณ  ๋‘ ๋ฐฐ์—ด ๋ชจ๋‘์— ๋Œ€ํ•ด โ€œ์ „์—ญ ๋ณ€์ˆ˜๋กœ ์ €์žฅโ€์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ๋ฐฐ์—ด์€ temp1, ๋‘ ๋ฒˆ์งธ ๋ฐฐ์—ด์ด temp2๋กœ ์ €์žฅ๋˜์—ˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ € ์ฝ˜์†”์—์„œ ๋‘ ๋ฐฐ์—ด์˜ ๊ฐ ์ข…์†์„ฑ์ด ๋™์ผํ•œ์ง€์— ๋Œ€ํ•ด ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Object.is(temp1[0], temp2[0]); // ๋ฐฐ์—ด ๊ฐ„์˜ ์ฒซ ๋ฒˆ์งธ ์ข…์†์„ฑ์ด ๋™์ผํ•ฉ๋‹ˆ๊นŒ?
Object.is(temp1[1], temp2[1]); // ๋ฐฐ์—ด ๊ฐ„์˜ ๋‘ ๋ฒˆ์งธ ์ข…์†์„ฑ์ด ๋™์ผํ•ฉ๋‹ˆ๊นŒ?
Object.is(temp1[2], temp2[2]); // ... ๊ทธ๋ฆฌ๊ณ  ๊ธฐํƒ€ ๋ชจ๋“  ์ข…์†์„ฑ๋“ค์ด ๋™์ผํ•ฉ๋‹ˆ๊นŒ? ...

๋ฉ”๋ชจ๋ฅผ ๋ฐฉํ•ดํ•˜๋Š” ์ข…์†์„ฑ์„ ๋ฐœ๊ฒฌํ•˜๋ฉด ์ œ๊ฑฐํ•  ๋ฐฉ๋ฒ•์„ ์ฐพ๊ฑฐ๋‚˜ ๋ฉ”๋ชจํ•  ๋ฐฉ๋ฒ•์„ ์ฐพ์œผ์„ธ์š”.


๋ฐ˜๋ณต๋ฌธ์—์„œ ๊ฐ ๋ชฉ๋ก ํ•ญ๋ชฉ์— ๋Œ€ํ•ด useMemo๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•˜๋Š”๋ฐ ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Chart ์ปดํฌ๋„ŒํŠธ๊ฐ€ memo๋กœ ๊ฐ์‹ธ์ ธ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ReportList ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋‹ค์‹œ ๋ Œ๋”๋ง ๋  ๋•Œ ๋ชฉ๋ก์˜ ๋ชจ๋“  Chart๋ฅผ ๋‹ค์‹œ ๋ Œ๋”๋งํ•˜๋Š” ๊ฒƒ์„ ์ƒ๋žตํ•˜๊ณ  ์‹ถ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋ฐ˜๋ณต๋ฌธ์—์„œ useMemo๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

function ReportList({ items }) {
return (
<article>
{items.map(item => {
// ๐Ÿ”ด ๋ฐ˜๋ณต๋ฌธ์—์„œ๋Š” useMemo๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
const data = useMemo(() => calculateReport(item), [item]);
return (
<figure key={item.id}>
<Chart data={data} />
</figure>
);
})}
</article>
);
}

๋Œ€์‹  ๊ฐ ํ•ญ๋ชฉ์— ๋Œ€ํ•œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ถ”์ถœํ•˜๊ณ  ๊ฐœ๋ณ„ ํ•ญ๋ชฉ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฉ”๋ชจํ•˜์„ธ์š”.

function ReportList({ items }) {
return (
<article>
{items.map(item =>
<Report key={item.id} item={item} />
)}
</article>
);
}

function Report({ item }) {
// โœ… ์ตœ์ƒ์œ„ ์ˆ˜์ค€์—์„œ useMemo๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
const data = useMemo(() => calculateReport(item), [item]);
return (
<figure>
<Chart data={data} />
</figure>
);
}

๋˜๋Š” useMemo๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  Report ์ž์ฒด๋ฅผ memo๋กœ ๊ฐ์‹ธ๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์Šต๋‹ˆ๋‹ค. item prop๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์œผ๋ฉด Report๋Š” ์žฌ๋ Œ๋”๋ง์„ ๊ฑด๋„ˆ๋›ฐ๋ฏ€๋กœ Chart ์—ญ์‹œ ์žฌ๋ Œ๋”๋ง์„ ๊ฑด๋„ˆ๋›ฐ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

function ReportList({ items }) {
// ...
}

const Report = memo(function Report({ item }) {
const data = calculateReport(item);
return (
<figure>
<Chart data={data} />
</figure>
);
});