https://tkdodo.eu/blog/the-useless-use-callback
The Useless useCallback
Why most memoization is downright useless...
tkdodo.eu
์๋ฌธ
๋ฉ๋ชจ์ด์ ์ด์
(memoization)์ ๋ํด์๋ ์ด์ ์ถฉ๋ถํ ๋ค๋ค๋ค๊ณ ์๊ฐํ๋๋ฐ, ์์ฆ ์์ฃผ ๋ณด์ด๋ ํ ๊ฐ์ง ํจํด์ ๋ณด๋ฉด์ ์๊ฐ์ด ๋ฌ๋ผ์ก๋ค. ๊ทธ๋์ ์ค๋์ useCallback ๊ณผ, ๋ถ๋ถ์ ์ผ๋ก๋ useMemo ์ ๋ํด ์ด์ผ๊ธฐํด๋ณด๋ ค ํ๋ค. ํนํ ๋ด๊ฐ ๋ณด๊ธฐ์ ์ ๋ง ๋ถํ์ํ๊ฒ ์ฌ์ฉ๋๋ ๊ฒฝ์ฐ๋ค ์ ๋ํด ๋ง์ด๋ค.
์ ๋ฉ๋ชจ์ด์ ์ด์
์ ์ฌ์ฉํ ๊น?
๋ณดํต useCallback์ผ๋ก ํจ์๋ฅผ , useMemo๋ก ๊ฐ์ ๋ฉ๋ชจ์ด์ ์ด์
ํ๋ ์ด์ ๋ ๋จ ๋ ๊ฐ์ง๋ฟ์ด๋ค.
1. ์ฑ๋ฅ ์ต์ ํ(Performance optimization)
์ด๋ค ๋์์ด ๋๋ฆด ๋, ์ฐ๋ฆฌ๋ ๋ณดํต ๊ทธ๊ฒ์ “๋์๋ค”๊ณ ๋๋๋ค. ์ด์์ ์ผ๋ก๋ ๋ ๋น ๋ฅด๊ฒ ๋ง๋๋ ๊ฒ ์ข์ง๋ง, ํญ์ ๊ทธ๋ ๊ฒ ํ ์๋ ์๋ค. ๋์ ๊ทธ ๋๋ฆฐ ๋์์ด ์ผ์ด๋๋ ํ์๋ฅผ ์ค์ด๋ ๋ฐฉ๋ฒ ์ ์๋ํ ์ ์๋ค.
React์์๋ ์ด๋ฐ “๋๋ฆฐ ๋์”์ด ์ฃผ๋ก ํ์ ํธ๋ฆฌ(sub-tree)์ ๋ฆฌ๋ ๋๋ง ์ด๋ค. ๋ฐ๋ผ์ “๊ตณ์ด ํ์ํ์ง ์๋ค”๊ณ ํ๋จ๋๋ฉด ์ด๋ฅผ ํผํ๊ณ ์ถ์ด์ง๋ค.
์ด๋ฐ ์ด์ ๋ก ๊ฐ๋ React.memo ๋ก ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ธ๊ธฐ๋ ํ๋ค. ๋ฌผ๋ก ์ด๋ ๋๋ถ๋ถ “๋
ธ๋ ฅ ๋๋น ํจ๊ณผ๊ฐ ํฌ์ง ์์ ์ธ์”์ด์ง๋ง, ๊ทธ๋๋ ์กด์ฌํ๋ ํ
ํฌ๋์ด๋ค.
๊ทธ๋ฐ๋ฐ ๋ฉ๋ชจ์ด์ ์ด์
๋ ์ปดํฌ๋ํธ ์ ํจ์๋ฅผ ์ ๋ฌํ๊ฑฐ๋, ์์ ํ์
์ด ์๋ ๊ฐ์ props๋ก ๋๊ธธ ๊ฒฝ์ฐ์ ์ฐธ์กฐ(reference)๊ฐ ์์ ์ ์ด์ด์ผ ํ๋ค. ๊ทธ ์ด์ ๋ React๊ฐ Object.is ๋ฅผ ์ฌ์ฉํด props๋ฅผ ๋น๊ตํ๊ณ , ๋์ผํ๋ค๊ณ ํ๋จ๋๋ฉด ํด๋น ํ์ ํธ๋ฆฌ์ ๋ฆฌ๋ ๋๋ง์ ๊ฑด๋๋ฐ๊ธฐ ๋๋ฌธ์ด๋ค.
๋ง์ฝ ์ด ์ฐธ์กฐ๊ฐ ์์ ์ ์ด์ง ์๋ค๋ฉด — ์๋ฅผ ๋ค์ด, ๋งค ๋ ๋๋ง๋ง๋ค ์๋กญ๊ฒ ์์ฑ๋๋ ๊ฐ์ด๋ผ๋ฉด — ์ฐ๋ฆฌ์ ๋ฉ๋ชจ์ด์ ์ด์
์ ๊นจ์ง๊ฒ ๋๋ค .
function Meh() {
return (
<MemoizedComponent
value={{ hello: 'world' }}
onChange={(result) => console.log('result')}
/>
)
}
function Okay() {
const value = useMemo(() => ({ hello: 'world' }), [])
const onChange = useCallback((result) => console.log(result), [])
return <MemoizedComponent value={value} onChange={onChange} />
}
๋ฌผ๋ก ๋๋ก๋ useMemo ๋ด๋ถ์ ๊ณ์ฐ ์์ฒด๊ฐ ๋๋ฆด ๋ ๊ฐ ์๋ค. ์ด๋ด ๋ ๋ถํ์ํ ์ฌ๊ณ์ฐ์ ํผํ๊ธฐ ์ํด ๋ฉ๋ชจ์ด์ ์ด์
์ ์ฌ์ฉํ๋๋ฐ, ์ด๋ฐ ๊ฒฝ์ฐ์ useMemo ์ฌ์ฉ์ ์ ํ ๋ฌธ์ ์๋ค. ํ์ง๋ง ์์งํ ๋งํ์๋ฉด, ์ด๋ฐ ๊ฒฝ์ฐ๊ฐ ๋๋ถ๋ถ์ ์๋๋ค.
2. Effect๊ฐ ๋ถํ์ํ๊ฒ ์์ฃผ ์คํ๋๋ ๊ฒ์ ๋ง๊ธฐ ์ํด
๋ฉ๋ชจ์ด์ ์ด์
๋ ๊ฐ์ ๋ฉ๋ชจ๋ ์ปดํฌ๋ํธ์ props๋ก ๋๊ธฐ์ง ์๋๋ผ๋, ๋๋ถ๋ถ์ ๊ฒฝ์ฐ ๊ทธ ๊ฐ์ ๊ฒฐ๊ตญ ์ด๋ค Effect์ ์์กด์ฑ(dependency) ์ผ๋ก ์ ๋ฌ๋๋ค. (๋๋ก๋ ์ฌ๋ฌ ๋จ๊ณ์ ์ปค์คํ
ํ
์ ๊ฑฐ์ณ์ ์ ๋ฌ๋๊ธฐ๋ ํ๋ค.)
Effect์ ์์กด์ฑ ๋น๊ต ๋ฐฉ์์ React.memo์ ๋์ผํ๋ค. React๋ ๊ฐ ์์กด์ฑ์ Object.is ๋ก ํ๋์ฉ ๋น๊ตํด, ๊ฐ์ด ๋ฐ๋์๋ค๋ฉด Effect๋ฅผ ๋ค์ ์คํํ๋ค.
๋ฐ๋ผ์ Effect์ ์์กด์ฑ ๊ฐ์ ๋ฉ๋ชจ์ด์ ์ด์
ํ์ง ์์ผ๋ฉด , ๋ ๋๋ง์ด ์ผ์ด๋ ๋๋ง๋ค Effect๊ฐ ๋งค๋ฒ ์ฌ์คํ๋๋ ์ํฉ ์ด ๋ฐ์ํ ์ ์๋ค.
๊ฐ๋งํ ์๊ฐํด๋ณด๋ฉด, ์ด ๋ ๊ฐ์ง ์ํฉ์ ์ฌ์ค ์์ ํ ๊ฐ์ ๋งฅ๋ฝ ์ด๋ผ๋ ๊ฑธ ์ ์ ์๋ค. ๋ ๋ค “๊ฐ์ ์ฐธ์กฐ(reference)๋ฅผ ์ ์งํด์ ์ด๋ค ์ผ์ด ์ผ์ด๋์ง ์๋๋ก ๋ง๋ ๊ฒ ”์ด ๋ชฉ์ ์ด๋ค.
์ฆ, useCallback์ด๋ useMemo๋ฅผ ์ฌ์ฉํ๋ ๊ฐ์ฅ ๊ณตํต๋ ์ด์ ๋ ๊ฒฐ๊ตญ ์ด๊ฑฐ๋ค:
์ฐธ์กฐ์ ์์ ์ฑ์ด ํ์ํ๋ค. ์ฆ, ๋งค ๋ ๋๋ง๋ง๋ค ๊ฐ์ด๋ ํจ์๊ฐ ์๋ก ์์ฑ๋์ง ์๊ณ ๊ฐ์ ์ฐธ์กฐ๋ฅผ ์ ์งํ๋๋ก ํ๋ ๊ฒ, ๊ทธ๊ฒ ๋ฐ๋ก useCallback๊ณผ useMemo๋ฅผ ์ฌ์ฉํ๋ ํต์ฌ ์ด์ ๋ค.
์ฐ๋ฆฌ ๋ชจ๋ ์ธ์์์ ์ด๋ ์ ๋์ ์์ ์ฑ(stability)์ ํ์ํ๋ค๊ณ ์๊ฐํ๋ค. ํ์ง๋ง ์ฒ์์ ๋งํ๋ฏ์ด, ๊ทธ ์์ ์ฑ์ ๊ตณ์ด ์ถ๊ตฌํ ํ์๊ฐ ์๋ ๊ฒฝ์ฐ , ์ฆ ‘์ธ๋ฐ์๋ ์์ ์ฑ ์ถ๊ตฌ’๋ ์ธ์ ์ผ๊น?
1. ๋ฉ๋ชจ์ด์ ์ด์
์ด ์๋ค๊ณ ํด์ ์ฑ๋ฅ ํฅ์์ด ์ฌ๋ผ์ง๋ ๊ฑด ์๋๋ค
์์ ์ดํด๋ณธ ์์์์, ์์ฃผ ์ฌ์ํ ๋ถ๋ถ๋ง ํ๋ฒ ๋ฐ๊ฟ๋ณด์.
no-memo
function Okay() {
const value = useMemo(() => ({ hello: 'world' }), [])
const onChange = useCallback((result) => console.log(result), [])
return <Component value={value} onChange={onChange} />
}
์ฐจ์ด์ ์ ์ฐพ์ ์ ์๋๊ฐ? ๋ง๋ค — ์ด์ value์ onChange๋ฅผ ๋ฉ๋ชจ์ด์ ์ด์
๋ ์ปดํฌ๋ํธ์ ์ ๋ฌํ์ง ์๋๋ค. ๊ทธ๋ฅ ์ผ๋ฐ์ ์ธ React ํจ์ํ ์ปดํฌ๋ํธ์ผ ๋ฟ์ด๋ค.
์ด๋ฐ ์ํฉ์ ํนํ ๊ฒฐ๊ตญ์ ๊ฐ์ด React์ ๊ธฐ๋ณธ(built-in) ์ปดํฌ๋ํธ ๋ก ์ ๋ฌ๋ ๋ ์์ฃผ ๋ฐ์ํ๋ค:
react-built-ins
function MyButton() {
const onClick = useCallback(
(event) => console.log(event.currentTarget.value),
[]
)
return <button onClick={onClick} />
}
์ฌ๊ธฐ์ onClick์ ๋ฉ๋ชจ์ด์ ์ด์
ํด๋ ์๋ฌด๋ฐ ํจ๊ณผ๊ฐ ์๋ค. ์๋ํ๋ฉด ๋ฒํผ ์ปดํฌ๋ํธ๋ onClick์ด ์ฐธ์กฐ๊ฐ ์์ ์ ์ธ์ง ์ฌ๋ถ๋ฅผ ์ ๊ฒฝ ์ฐ์ง ์๊ธฐ ๋๋ฌธ์ด๋ค.
“์๋ฌด ํจ๊ณผ๊ฐ ์๋ค(Achieves nothing)”๋ผ๊ณ ํํํ์ง๋ง, ์๋ฐํ ๋งํ๋ฉด ์ฝ๊ฐ ์๋ชป๋ ํํ์ด๋ค. ์ฌ์ค ๋ด๋ถ์ ์ผ๋ก๋ ๋ถ๋ช
๋ฌด์ธ๊ฐ ์ผ์ด ์ผ์ด๋๊ณ ์๋ค. React๋ onClick ํจ์๋ฅผ ์ ์งํ๊ธฐ ์ํด ์บ์๋ฅผ ๋ง๋ค์ด์ผ ํ๊ณ , ์์กด์ฑ์ ์ถ์ ํ๋ฉฐ ๋งค ๋ ๋๋ง๋ง๋ค ๋น๊ต๋ฅผ ์ํํ๋ค. useCallback์ ์ ๋ฌ๋ ์ธ๋ผ์ธ ํจ์๋ ๋งค ๋ ๋๋ง๋ค ์๋ก ์์ฑ๋์ง๋ง, ์บ์๋ ๋ฒ์ ์ด ๋ฐํ๋๋ฉด ์ฆ์ ๋ฒ๋ ค์ง๋ค. ๊ฒฐ๊ณผ์ ์ผ๋ก, ๊ธฐ์ ์ ์ผ๋ก๋ ๋ด๋ถ์ ์ฝ๊ฐ์ ์ค๋ฒํค๋๊ฐ ์๊ธด๋ค. ํ์ง๋ง ๋๋ ์ด ์ค๋ฒํค๋์ ๋๋ฌด ์ง์คํ๊ณ ์ถ์ง ์๋ค. ์๋ํ๋ฉด ์ง์ง ๋ฌธ์ ๋ ๊ทธ๊ฒ ์๋๊ธฐ ๋๋ฌธ์ด๋ค.
์ฆ, ์ฌ์ฉ์ ์ ์ ์ปดํฌ๋ํธ๊ฐ ๋ฉ๋ชจ์ด์ ์ด์
๋์ด ์์ง ์๋ค๋ฉด , ์ฐธ์กฐ ์์ ์ฑ(referential stability)์ ์ฌ์ค ์ ๊ฒฝ ์ธ ํ์๊ฐ ์๋ค๋ ๋ป์ด๋ค.
ํ์ง๋ง ์ ๊น — ๋ง์ฝ ๊ทธ ์ปดํฌ๋ํธ๊ฐ props๋ฅผ ๋ด๋ถ์์ useEffect์ ์์กด์ฑ์ผ๋ก ์ฌ์ฉํ๊ฑฐ๋ , ๋ค๋ฅธ ๋ฉ๋ชจ์ด์ ์ด์
๋ ๊ฐ์ ๋ง๋ค์ด ๊ทธ ๊ฐ์ ์์ ์ปดํฌ๋ํธ์ ์ ๋ฌ ํ๋ค๋ฉด ์ด๋ป๊ฒ ๋ ๊น?
์ด ๊ฒฝ์ฐ, ์ง๊ธ useMemo๋ useCallback์ ์ ๊ฑฐํ๋ฉด , ๊ธฐ์กด ๋ฉ๋ชจ์ด์ ์ด์
์ ์ ๊ฑฐํ๋ฉด ๋ฌด์ธ๊ฐ๊ฐ ๊นจ์ง ์๋ ์๋ค.
๋ฐ๋ก ์ด ์ ์ด ๋ ๋ฒ์งธ ํฌ์ธํธ๋ก ์ด์ด์ง๋ค.
2. props๋ฅผ ์์กด์ฑ์ผ๋ก ์ฌ์ฉํ ๋
์ปดํฌ๋ํธ์ ์ ๋ฌ๋ฐ์ ์์ ํ์
์ด ์๋(non-primitive) ๋น์์๊ฐ props๋ฅผ ๋ด๋ถ useEffect๋ useMemo ๋ฑ์ ์์กด์ฑ ๋ฐฐ์ด์ ๊ทธ๋๋ก ๋ฃ๋ ๊ฑด ์ ๊ฑฐ์ ๋๋ถ๋ถ ์ ์ ํ์ง ์๋ค.
์๋ํ๋ฉด ์ด ์ปดํฌ๋ํธ๋ ๊ทธ props์ ์ฐธ์กฐ ์์ ์ฑ์ ๋ํด ์ด๋ค ํต์ ๊ถ๋ ์๊ธฐ ๋๋ฌธ์ด๋ค.
ํํ ์์๋ ๋ค์๊ณผ ๊ฐ๋ค.
props-as-dependencies
function OhNo({ onChange }) {
const handleChange = useCallback((e: React.ChangeEvent) => {
trackAnalytics('changeEvent', e)
onChange?.(e)
}, [onChange])
return <SomeMemoizedComponent onChange={handleChange} />
}
์ด useCallback์ ์๋ง ์ธ๋ชจ๊ฐ ์๊ฑฐ๋ , ๊ธฐ๊ปํด์ผ ์ด ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ๋ ์ชฝ์ด ์ด๋ป๊ฒ ์ฌ์ฉํ๋๋ ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง ๋ฟ์ด๋ค.
๋๋ถ๋ถ์ ๊ฒฝ์ฐ, ํธ์ถํ๋ ์ชฝ์์ ๊ทธ๋ฅ ์ธ๋ผ์ธ ํจ์๋ฅผ ํธ์ถ ํ ๊ฐ๋ฅ์ฑ์ด ๋๋ค.
<OhNo onChange={() => props.doSomething()} />โ
์ด๊ฑด ๋ฌดํดํ ์ฌ์ฉ ์ฌ๋ก ๋ค. ์ ํ ๋ฌธ์ ๋ ๊ฒ ์๋ค. ์ฌ์ค ์คํ๋ ค ์ข์ ํจํด ์ด๋ผ๊ณ ๋ณผ ์ ์๋ค. ์ด๋ฒคํธ ํธ๋ค๋ฌ์ ๊ด๋ จ๋ ๋์์ ํ๊ณณ์ ๋ชจ์๋์ด, ํ์ผ ์๋จ์ผ๋ก ๋์ด์ฌ๋ ค์ ๊ธธ๊ฒ ์ด๋ฆ ๋ถ์ธ handleChange ๊ฐ์ ํจ์๋ฅผ ๋ง๋๋ ์ผ์ ํผํ ์ ์๋ค.
์ด ์ฝ๋๋ฅผ ์์ฑํ ๊ฐ๋ฐ์๊ฐ ๋ฉ๋ชจ์ด์ ์ด์
์ด ๊นจ์ง๋ค๋ ์ฌ์ค์ ์ ์ ์๋ ์ ์ผํ ๋ฐฉ๋ฒ์, ์ปดํฌ๋ํธ ๋ด๋ถ๋ฅผ ๊น์ด ํ๊ณ ๋ค์ด๊ฐ props๊ฐ ์ด๋ป๊ฒ ์ฌ์ฉ๋๋์ง ํ์ธํ๋ ๊ฒ ๋ฟ์ด๋ค. ์์งํ ๋งํด, ๊ทธ๊ฑด ๋์ฐํ๋ค.
๋ค๋ฅธ ํด๊ฒฐ์ฑ
์ผ๋ก๋ “๋ชจ๋ ๊ฑธ ํญ์ ๋ฉ๋ชจ์ด์ ์ด์
ํ๋ค”๋ผ๋ ์ ์ฑ
์ ์ธ์ฐ๊ฑฐ๋, ์ฐธ์กฐ ์์ ์ฑ์ด ํ์ํ props์๋ mustBeMemoized ๊ฐ์ ์๊ฒฉํ ๋ค์ด๋ฐ ๊ท์น์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ด ์๋ค.ํ์ง๋ง ์ด ๋ ๋ค ์ข์ ๋ฐฉ๋ฒ์ ์๋๋ค.
์ค์ ์ฌ๋ก(Real Life Example)
์ง๊ธ ๋๋ Sentry ์ฝ๋๋ฒ ์ด์ค ์์ ์์
ํ๊ณ ์๋๋ฐ, ์ด๊ฑด ์คํ์์ค ๋ผ์ ๐ ์ค์ ์ฌ์ฉ ์ฌ๋ก๋ฅผ ์ฝ๊ฒ ๋ณด์ฌ์ค ์ ์๋ค.
๋ด๊ฐ ๋ฐ๊ฒฌํ ํ ๊ฐ์ง ์ํฉ์ useHotkeys ์ปค์คํ
ํ
์ด๋ค. ํต์ฌ ๋ถ๋ถ์ ๋๋ต ์ด๋ ๊ฒ ์๊ฒผ๋ค:
useHotkeys
export function useHotkeys(hotkeys: Hotkey[]): {
const onKeyDown = useCallback(() => ..., [hotkeys])
useEffect(() => {
document.addEventListener('keydown', onKeyDown)
return () => {
document.removeEventListener('keydown', onKeyDown)
}
}, [onKeyDown])
}
์ด ์ปค์คํ
ํ
์ hotKeys ๋ฐฐ์ด์ ์ธ์๋ก ๋ฐ๊ณ ,๊ทธ๊ฑธ ๊ธฐ๋ฐ์ผ๋ก onKeyDown ํจ์๋ฅผ ๋ฉ๋ชจ์ด์ ์ด์
ํ ๋ค, ํด๋น ํจ์๋ฅผ effect์ ๋๊ธด๋ค.
ํจ์๋ฅผ ๋ฉ๋ชจ์ด์ ์ด์
ํ๋ ์ด์ ๋ ๋ช
๋ฐฑํ๋ค: Effect๊ฐ ๋๋ฌด ์์ฃผ ์คํ๋๋ ๊ฒ์ ๋ฐฉ์ง ํ๊ธฐ ์ํด์๋ค.
ํ์ง๋ง ์
๋ ฅ๊ฐ์ธ hotkeys๊ฐ ๋ฐฐ์ด์ด๊ธฐ ๋๋ฌธ์, ์ฌ์ฉํ๋ ์ชฝ์์ ์ง์ hotkeys๋ฅผ ๋ฉ๋ชจ์ด์ ์ด์
ํด์ฃผ์ด์ผ ํ๋ค.
๊ทธ๋์ ๋๋ useHotkeys๊ฐ ์ค์ ์ฝ๋์์ ์ด๋ป๊ฒ ์ฐ์ด๊ณ ์๋์ง ์ ์ ์กฐ์ฌ๋ฅผ ํด๋ดค๋ค. ๋๋๊ฒ๋ ๊ฑฐ์ ๋๋ถ๋ถ(ํ ๊ณณ์ ์ ์ธํ๊ณ )์ ์
๋ ฅ๊ฐ์ ๋ฉ๋ชจ์ด์ ์ด์
ํ๊ณ ์์๋ค.
ํ์ง๋ง ์ด์ผ๊ธฐ๋ ์ฌ๊ธฐ์ ๋๋์ง ์๋๋ค.๋ ๊น์ด ๋ค์ด๊ฐ ๋ณด๋ฉด, ๊ฒฐ๊ตญ์๋ ์ฌ์ ํ ๋ฌธ์ ๊ฐ ์๊ธฐ๊ธฐ ์ฌ์ด ๊ตฌ์กฐ๋ผ๋ ๊ฑธ ์ ์ ์๋ค. ์๋ฅผ ๋ค์ด, ๋ค์๊ณผ ๊ฐ์ ์ฌ์ฉ ์ฌ๋ก๋ฅผ ๋ณด์.
paginateHotKeys
const paginateHotkeys = useMemo(() => {
return [
{ match: 'right', callback: () => paginateItems(1) },
{ match: 'left', callback: () => paginateItems(-1) },
]
}, [paginateItems])
useHotkeys(paginateHotkeys)
useHotKeys๋ ๋ฉ๋ชจ์ด์ ์ด์
๋ paginateHotkeys ๋ฅผ ์ ๋ฌํ๋ค. ๊ทธ๋ฐ๋ฐ ์ด paginateHotkeys๋ paginateItems ์ ์์กดํ๋ค.
๊ทธ๋ผ paginateItems๋ ์ด๋์ ์ฌ๊น?
๋ฐ๋ก ๋ ๋ค๋ฅธ useCallback์ด๊ณ , ์ด ์ฝ๋ฐฑ์ screenshots์ currentAttachmentIndex์ ์์กดํ๋ค.
๊ทธ๋ ๋ค๋ฉด screenshots๋ ์ด๋์ ์ค๋ ๊ฑธ๊น?
breaking-memoization
const screenshots = attachments.filter(({ name }) =>
name.includes('screenshot')
)
screenshots๋ ๋ฉ๋ชจ์ด์ ์ด์
๋์ง ์์ attachments.filter ํจ์ ์์ ์์ฑ๋๋ค. ์ด ํจ์๋ ๋งค๋ฒ ์๋ก์ด ๋ฐฐ์ด(Array)์ ์์ฑ ํ๋ฏ๋ก, downstream์ ์๋ ๋ชจ๋ ๋ฉ๋ชจ์ด์ ์ด์
์ด ๊นจ์ง๋ค.
๊ฒฐ๊ณผ์ ์ผ๋ก ๋ชจ๋ ๋ฉ๋ชจ์ด์ ์ด์
์ด ๋ฌด์ฉ์ง๋ฌผ์ด ๋๋ค.
์ฆ, paginateItems , paginateHotkeys , onKeyDown ์ด ์ธ ๊ฐ์ง ๋ฉ๋ชจ์ด์ ์ด์
์ด ๋งค ๋ ๋๋ง๋ง๋ค ๋ค์ ์คํ๋๋ฉฐ, ๋ง์น ์์ ์์ฑํ์ง ์์ ๊ฒ๊ณผ ๋์ผํ ์ํฉ์ด ๋๋ค! (์ด๋ ๊ฒ ๋๋ฉด ์ฐ๋ฆฌ๊ฐ ํ๋ ๋ฉ๋ชจ์ด์ ์ด์
์ ์ ๋ถ ๋ฌด์ฉ์ง๋ฌผ์ด ๋๋ค)
์ด ์์๊ฐ ๋ด๊ฐ ์ ๋ฉ๋ชจ์ด์ ์ด์
์ ์ฉ์ ๋ฐ๋ํ๋ ์
์ฅ์ธ์ง ์ ๋ณด์ฌ์คฌ์ผ๋ฉด ํ๋ค. ๋ด ๊ฒฝํ์, ๋ฉ๋ชจ์ด์ ์ด์
์ ๋๋ฌด ์์ฃผ ๊นจ์ง๊ณ , ๊ทธ๋ด ๊ฐ์น๊ฐ ์๋ค.
๊ฒ๋ค๊ฐ ์ฝ๋ ์ ์ฒด์ ์์ฒญ๋ ์ค๋ฒํค๋์ ๋ณต์ก์ฑ ์ ์ถ๊ฐํ๋ค.
์ฌ๊ธฐ์ ํด๊ฒฐ์ฑ
์ด screenshots๊น์ง ๋ฉ๋ชจ์ด์ ์ด์
ํ๋ ๊ฒ์ ์๋๋ค.
๊ทธ๋ ๊ฒ ํ๋ฉด ์ฑ
์์ด ๊ฒฐ๊ตญ ์ปดํฌ๋ํธ์ prop์ธ attachments๋ก ๋์ด๊ฐ๊ฒ ๋๋ค. ์ธ ๊ฐ์ง ํธ์ถ ์ง์ (call-side) ๋ชจ๋์์ ์ค์ ๋ก ๋ฉ๋ชจ์ด์ ์ด์
์ด ํ์ํ useHotkeys์๋ ์ต์ ๋ ๋จ๊ณ ์ด์ ๋จ์ด์ ธ ์๊ฒ ๋๋ค.
์ด๊ฑด ์ฝ๋๋ฅผ ํ์ํ๊ธฐ ์
๋ชฝ ๊ฐ์ ์ํฉ์ ๋ง๋ค๋ฉฐ, ๊ฒฐ๊ตญ ์๋ฌด๋ ๋ฉ๋ชจ์ด์ ์ด์
ํ๋๋ฅผ ์ ๊ฑฐํ์ง ๋ชปํ๊ฒ ๋๋ค. ์๋ํ๋ฉด ๊ทธ ๋ฉ๋ชจ์ด์ ์ด์
์ด ์ค์ ๋ก ๋ฌด์์ ํ๊ณ ์๋์ง ์ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
์ค์ ๋ก๋, ์ด๋ฐ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ค๋ฉด ์ปดํ์ผ๋ฌ์๊ฒ ๋ชจ๋ ๊ฑธ ๋งก๊ธฐ๋ ๊ฒ ์ด ๊ฐ์ฅ ์ข๋ค.
๋ชจ๋ ํ๊ฒฝ์์ ์ ์๋ํ๋ค๋ฉด ์ ๋ง ํ๋ฅญํ๋ค. ํ์ง๋ง ๊ทธ ์ ๊น์ง๋, ์ฐธ์กฐ ์์ ์ฑ(referential stability) ํ์์ฑ์ ํ๊ณ๋ฅผ ์ฐํํ ํจํด ์ ์ฐพ์์ผ ํ๋ค.
์ต์ Ref ํจํด(The Latest Ref Pattern)
์ด ํจํด์ ๋ํด์๋ ์ด์ ์ ์ธ๊ธํ ์ ์ด ์๋ค. ํต์ฌ ์์ด๋์ด๋, Effect ์์์ ์ฆ์ ์ ๊ทผํ๊ณ ์ถ์ ๊ฐ์ ref ์์ ์ ์ฅ ํด๋๊ณ , ๋ ๋ค๋ฅธ Effect๋ฅผ ํตํด ์๋์ ์ผ๋ก ๋งค ๋ ๋๋ง๋ง๋ค ๊ฐ์ ์
๋ฐ์ดํธ ํ๋ ๊ฒ์ด๋ค.
the-latest-ref
export function useHotkeys(hotkeys: Hotkey[]): {
const hotkeysRef = useRef(hotkeys)
useEffect(() => {
hotkeysRef.current = hotkeys
})
const onKeyDown = useCallback(() => ..., [])
useEffect(() => {
document.addEventListener('keydown', onKeyDown)
return () => {
document.removeEventListener('keydown', onKeyDown)
}
}, [])
}
๊ทธ๋ ๊ฒ ํ๋ฉด hotkeysRef๋ฅผ Effect ์์์ ๋ฐ๋ก ์ฌ์ฉํ ์ ์๋ค. ์์กด์ฑ ๋ฐฐ์ด์ ์ถ๊ฐํ ํ์๋ ์๊ณ , ๋จ์ํ ๋ฆฐํฐ๋ฅผ ๋ฌด์ํ๋ค๊ฐ ๋ฐ์ํ ์ ์๋ stale closure ๋ฌธ์ ๋ ๊ฑฑ์ ํ์ง ์์๋ ๋๋ค.
์ค์ ๋ก React Query ๋ ์ด ํจํด์ ์ฌ์ฉํ๋ค.
์๋ฅผ ๋ค์ด PersistQueryClientProvider๋ useMutationState์์ ์ต์ ์ต์
(latest options)์ ์ถ์ ํ ๋ ๊ทธ๋ ๋ค. ์ฆ, ๊ฒ์ฆ๋(tried-and-true) ํจํด ์ด๋ผ๊ณ ๋ณผ ์ ์๋ค. ๋ง์ฝ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ฌ์ฉ์๊ฐ ์ต์
์ ์ง์ ๋ฉ๋ชจ์ด์ ์ด์
ํ๋๋ก ๊ฐ์ ํ๋ค๋ฉด ์ผ๋ง๋ ๊ท์ฐฎ์์ง ์์ํด๋ณด๋ผ.
UseEffectEvent
์ข์ ์์์ด ํ๋ ๋ ์๋ค. React ํ์ ๋๋ถ๋ถ์ ๊ฒฝ์ฐ, ๋ฆฌ์กํฐ๋ธ Effect ์์์ ์ต์ ๊ฐ์ ๋ช
๋ นํ(imperative)์ผ๋ก ์ ๊ทผํ ํ์๊ฐ ์์ง๋ง, Effect๋ฅผ ๋ช
์์ ์ผ๋ก ๋ค์ ์คํํ๊ณ ์ถ์ง ์์ ์ํฉ ์ด ์๋ค๋ ๊ฒ์ ์ธ์ํ๋ค.
๊ทธ๋์ ์ด ํจํด์ ์ํ 1๊ธ ํ๋ฆฌ๋ฏธํฐ๋ธ(first-class primitive) , useEffectEvent ๋ฅผ ์๋ก ์ถ๊ฐํ ์์ ์ด๋ค.
์ด ํ
์ด ๋์
๋๋ฉด, ๊ธฐ์กด ์ฝ๋๋ฅผ ๋ณด๋ค ๊น๋ํ๊ณ ์์ ํ๊ฒ ๋ฆฌํฉํฐ๋งํ ์ ์๊ฒ ๋๋ค:
the-latest-ref
export function useHotkeys(hotkeys: Hotkey[]): {
const onKeyDown = useEffectEvent(() => ...)
useEffect(() => {
document.addEventListener('keydown', onKeyDown)
return () => {
document.removeEventListener('keydown', onKeyDown)
}
}, [])
}
์ด๋ ๊ฒ ํ๋ฉด onKeyDown ์ ๋ฆฌ์กํฐ๋ธํ์ง ์๊ฒ ๋ง๋ค ์ ์๋ค. ํ์ง๋ง ์ฌ์ ํ hotkeys์ ์ต์ ๊ฐ์ ํญ์ “๋ณผ ์ ์๊ฒ” ๋๋ฉฐ, ๋ ๋ ์ฌ์ด์์๋ ์ฐธ์กฐ ์์ ์ฑ(referential stability) ์ ์ ์งํ๋ค.
์ฆ, useCallback์ด๋ useMemo๋ฅผ ๋จ ํ๋๋ ์ฐ์ง ์๊ณ ๋, ๋ชจ๋ ์ฅ์ ์ ๋์์ ์ป์ ์ ์๊ฒ ๋๋ ๊ฒ์ด๋ค.
์ ๋ฆฌ
๋จ์ํ ์ฑ๋ฅ ์ต์ ํ๋ ์์ ์ฑ์ ์ด์ ๋ก ๋ฌด์์ useMemo/useCallback์ ์ฌ์ฉํ๋ ๊ฒ์ ๋๋ถ๋ถ ๋ถํ์ํ๊ฑฐ๋ ์คํ๋ ค ๋ฌธ์ ๋ฅผ ๋ง๋ ๋ค. ๋์ Ref๋ฅผ ํ์ฉํ๊ฑฐ๋ ์๋ก์ด React ํจํด์ ์ฌ์ฉํด ์ต์ ๊ฐ์ ์์ ํ๊ฒ ์ฐธ์กฐํ๋ ๋ฐฉ๋ฒ ์ ์ฐ๋ ๊ฒ ์ข๋ค.