useCallback์€ ๋ฆฌ๋ Œ๋”๋ง ๊ฐ„์— ํ•จ์ˆ˜ ์ •์˜๋ฅผ ์บ์‹ฑํ•ด ์ฃผ๋Š” React Hook์ž…๋‹ˆ๋‹ค.

const cachedFn = useCallback(fn, dependencies)

๋ ˆํผ๋Ÿฐ์Šค

useCallback(fn, dependencies)

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

import { useCallback } from 'react';

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

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

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

  • fn: ์บ์‹ฑํ•  ํ•จ์ˆซ๊ฐ’์ž…๋‹ˆ๋‹ค. ์ด ํ•จ์ˆ˜๋Š” ์–ด๋–ค ์ธ์ž๋‚˜ ๋ฐ˜ํ™˜๊ฐ’๋„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. React๋Š” ์ฒซ ๋ Œ๋”๋ง์—์„œ ์ด ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. (ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹™๋‹ˆ๋‹ค!) ๋‹ค์Œ ๋ Œ๋”๋ง์—์„œ dependencies ๊ฐ’์ด ์ด์ „๊ณผ ๊ฐ™๋‹ค๋ฉด React๋Š” ๊ฐ™์€ ํ•จ์ˆ˜๋ฅผ ๋‹ค์‹œ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋Œ€๋กœ dependencies ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค๋ฉด ์ด๋ฒˆ ๋ Œ๋”๋ง์—์„œ ์ „๋‹ฌํ•œ ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  ๋‚˜์ค‘์— ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ด๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. React๋Š” ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด ํ•จ์ˆ˜๋Š” ํ˜ธ์ถœ ์—ฌ๋ถ€์™€ ํ˜ธ์ถœ ์‹œ์ ์„ ๊ฐœ๋ฐœ์ž๊ฐ€ ๊ฒฐ์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค.

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

๋ฐ˜ํ™˜๊ฐ’

์ตœ์ดˆ ๋ Œ๋”๋ง์—์„œ๋Š” useCallback์€ ์ „๋‹ฌํ•œ fnํ•จ์ˆ˜๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

ํ›„์† ๋ Œ๋”๋ง์—์„œ๋Š” ์ด์ „ ๋ Œ๋”๋ง์—์„œ ์ด๋ฏธ ์ €์žฅํ•ด ๋‘์—ˆ๋˜ fnํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜ (์˜์กด์„ฑ์ด ๋ณ€ํ•˜์ง€ ์•Š์•˜์„ ๋•Œ), ํ˜„์žฌ ๋ Œ๋”๋ง ์ค‘์— ์ „๋‹ฌํ•œ fnํ•จ์ˆ˜๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

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

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

์šฉ๋ฒ•

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

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

์ปดํฌ๋„ŒํŠธ์˜ ๋ฆฌ๋ Œ๋”๋ง ๊ฐ„์— ํ•จ์ˆ˜๋ฅผ ์บ์‹ฑํ•˜๋ ค๋ฉด ํ•จ์ˆ˜ ์ •์˜๋ฅผ useCallback Hook์œผ๋กœ ๊ฐ์‹ธ์„ธ์š”.

import { useCallback } from 'react';

function ProductPage({ productId, referrer, theme }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
// ...

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

  1. ๋ฆฌ๋ Œ๋”๋ง ๊ฐ„์— ์บ์‹ฑํ•  ํ•จ์ˆ˜ ์ •์˜
  2. ํ•จ์ˆ˜์—์„œ ์‚ฌ์šฉ๋˜๋Š” ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€์˜ ๋ชจ๋“  ๊ฐ’์„ ํฌํ•จํ•˜๊ณ  ์žˆ๋Š” ์˜์กด์„ฑ ๋ชฉ๋ก

์ตœ์ดˆ ๋ Œ๋”๋ง์—์„œ useCallback์œผ๋กœ๋ถ€ํ„ฐ ๋ฐ˜ํ™˜๋˜๋Š” ํ•จ์ˆ˜๋Š” ํ˜ธ์ถœ์‹œ์— ์ „๋‹ฌํ•  ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.

์ด์–ด์ง€๋Š” ๋ Œ๋”๋ง์—์„œ React๋Š” ์˜์กด์„ฑ์„ ์ด์ „ ๋ Œ๋”๋ง์—์„œ ์ „๋‹ฌํ•œ ์˜์กด์„ฑ๊ณผ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. ์˜์กด์„ฑ ์ค‘ ํ•˜๋‚˜๋ผ๋„ ๋ณ€ํ•œ ๊ฐ’์ด ์—†๋‹ค๋ฉด(Object.is๋กœ ๋น„๊ต), useCallback์€ ์ „๊ณผ ๋˜‘๊ฐ™์€ ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด useCallback์€ ์ด๋ฒˆ ๋ Œ๋”๋ง์—์„œ ์ „๋‹ฌํ•œ ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์‹œ ๋งํ•˜๋ฉด, useCallback์€ ์˜์กด์„ฑ์ด ๋ณ€ํ•˜๊ธฐ ์ „๊นŒ์ง€ ๋ฆฌ๋ Œ๋”๋ง ๊ฐ„์— ํ•จ์ˆ˜๋ฅผ ์บ์‹ฑํ•ฉ๋‹ˆ๋‹ค.

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

handleSubmit ํ•จ์ˆ˜๋ฅผ ProductPage์—์„œ ShippingForm ์ปดํฌ๋„ŒํŠธ๋กœ ์ „๋‹ฌํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ด…์‹œ๋‹ค.

function ProductPage({ productId, referrer, theme }) {
// ...
return (
<div className={theme}>
<ShippingForm onSubmit={handleSubmit} />
</div>
);

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

๊ธฐ๋ณธ์ ์œผ๋กœ, ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋งํ•  ๋•Œ React๋Š” ์ด๊ฒƒ์˜ ๋ชจ๋“  ์ž์‹์„ ์žฌ๊ท€์ ์œผ๋กœ ์žฌ๋žœ๋”๋งํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ProductPage๊ฐ€ ๋‹ค๋ฅธ theme ๊ฐ’์œผ๋กœ ๋ฆฌ๋ Œ๋”๋ง ํ•  ๋•Œ, ShippingForm ์ปดํฌ๋„ŒํŠธ ๋˜ํ•œ ๋ฆฌ๋ Œ๋”๋ง ํ•˜๋Š” ์ด์œ ์ž…๋‹ˆ๋‹ค. ์ด ๊ฒƒ์€ ๋ฆฌ๋ Œ๋”๋ง์— ๋งŽ์€ ๊ณ„์‚ฐ์„ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š” ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ฆฌ๋ Œ๋”๋ง์ด ๋Š๋ฆฐ ๊ฒƒ์„ ํ™•์ธํ•œ ๊ฒฝ์šฐ, ShippingForm์„ memo๋กœ ๊ฐ์‹ธ๋ฉด ๋งˆ์ง€๋ง‰ ๋ Œ๋”๋ง๊ณผ ๋™์ผํ•œ props์ผ ๋•Œ ๋ฆฌ๋ Œ๋”๋ง์„ ๊ฑด๋„ˆ๋›ฐ๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { memo } from 'react';

const ShippingForm = memo(function ShippingForm({ onSubmit }) {
// ...
});

์ด๋ ‡๊ฒŒ ๋ณ€๊ฒฝํ•œ ShippingForm์€ ๋ชจ๋“  props๊ฐ€ ๋งˆ์ง€๋ง‰ ๋ Œ๋”๋ง๊ณผ ๊ฐ™๋‹ค๋ฉด ๋ฆฌ๋ Œ๋”๋ง์„ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ๊ฐ€ ํ•จ์ˆ˜ ์บ์‹ฑ์ด ์ค‘์š”ํ•ด์ง€๋Š” ์ˆœ๊ฐ„์ž…๋‹ˆ๋‹ค! useCallback ์—†์ด handleSubmit์„ ์ •์˜ํ–ˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ด…์‹œ๋‹ค.

function ProductPage({ productId, referrer, theme }) {
// theme์ด ๋ฐ”๋€”๋•Œ๋งˆ๋‹ค ๋‹ค๋ฅธ ํ•จ์ˆ˜๊ฐ€ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค...
function handleSubmit(orderDetails) {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}

return (
<div className={theme}>
{/* ... ๊ทธ๋ž˜์„œ ShippingForm์˜ props๋Š” ๊ฐ™์€ ๊ฐ’์ด ์•„๋‹ˆ๋ฏ€๋กœ ๋งค๋ฒˆ ๋ฆฌ๋ Œ๋”๋ง ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.*/}
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}

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

function ProductPage({ productId, referrer, theme }) {
// React์—๊ฒŒ ๋ฆฌ๋ Œ๋”๋ง ๊ฐ„์— ํ•จ์ˆ˜๋ฅผ ์บ์‹ฑํ•˜๋„๋ก ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค...
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]); // ...์ด ์˜์กด์„ฑ์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š” ํ•œ...

return (
<div className={theme}>
{/* ...ShippingForm์€ ๊ฐ™์€ props๋ฅผ ๋ฐ›๊ฒŒ ๋˜๊ณ  ๋ฆฌ๋ Œ๋”๋ง์„ ๊ฑด๋„ˆ๋›ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.*/}
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}

handleSubmit์„ useCallback์œผ๋กœ ๊ฐ์Œˆ์œผ๋กœ์จ ๋ฆฌ๋ Œ๋”๋ง ๊ฐ„์— ์ด๊ฒƒ์ด (์˜์กด์„ฑ์ด ๋ณ€๊ฒฝ๋˜๊ธฐ ์ „๊นŒ์ง€๋Š”) ๊ฐ™์€ ํ•จ์ˆ˜๋ผ๋Š” ๊ฒƒ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. ํŠน๋ณ„ํ•œ ์ด์œ ๊ฐ€ ์—†๋‹ค๋ฉด ํ•จ์ˆ˜๋ฅผ ๊ผญ useCallback์œผ๋กœ ๊ฐ์Œ€ ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. ์ด ์˜ˆ์‹œ์—์„œ์˜ ์ด์œ ๋Š” โ€˜memoโ€™๋กœ ๊ฐ์‹ผ ์ปดํฌ๋„ŒํŠธ์— ์ „๋‹ฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ํ•จ์ˆ˜๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง์„ ๊ฑด๋„ˆ๋›ธ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. useCallback์ด ํ•„์š”ํ•œ ๋‹ค๋ฅธ ์ด์œ ๋Š” ์ด ํŽ˜์ด์ง€์˜ ๋’ท๋ถ€๋ถ„์—์„œ ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

Note

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

Deep Dive

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

import { useMemo, useCallback } from 'react';

function ProductPage({ productId, referrer }) {
const product = useData('/product/' + productId);

const requirements = useMemo(() => { // ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ์บ์‹ฑํ•ฉ๋‹ˆ๋‹ค.
return computeRequirements(product);
}, [product]);

const handleSubmit = useCallback((orderDetails) => { // ํ•จ์ˆ˜ ์ž์ฒด๋ฅผ ์บ์‹ฑํ•ฉ๋‹ˆ๋‹ค.
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);

return (
<div className={theme}>
<ShippingForm requirements={requirements} onSubmit={handleSubmit} />
</div>
);
}

์ฐจ์ด์ ์€ ๋ฌด์—‡์„ ์บ์‹ฑํ•˜๋Š”์ง€ ์ž…๋‹ˆ๋‹ค.

  • useMemo ๋Š” ํ˜ธ์ถœํ•œ ํ•จ์ˆ˜์˜ ๊ฒฐ๊ณผ๊ฐ’์„ ์บ์‹ฑํ•ฉ๋‹ˆ๋‹ค. ์ด ์˜ˆ์‹œ์—์„œ๋Š” computeRequirements(product) ํ•จ์ˆ˜ ํ˜ธ์ถœ ๊ฒฐ๊ณผ๋ฅผ ์บ์‹ฑํ•ด์„œ product๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š” ํ•œ ์ด ๊ฒฐ๊ณผ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ShippingForm์„ ๋ฆฌ๋ Œ๋”๋งํ•˜์ง€ ์•Š๊ณ  requirements ๊ฐ์ฒด๋ฅผ ๋„˜๊ฒจ์ค„ ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค๋‹ˆ๋‹ค. ํ•„์š”ํ•  ๋•Œ React๋Š” ๋ Œ๋”๋ง ์ค‘์— ๋„˜๊ฒจ์ฃผ์—ˆ๋˜ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๊ฒฐ๊ณผ๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
  • useCallback์€ ํ•จ์ˆ˜ ์ž์ฒด๋ฅผ ์บ์‹ฑํ•ฉ๋‹ˆ๋‹ค. useMemo์™€ ๋‹ฌ๋ฆฌ, ์ „๋‹ฌํ•œ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ทธ ๋Œ€์‹ , ์ „๋‹ฌํ•œ ํ•จ์ˆ˜๋ฅผ ์บ์‹ฑํ•ด์„œ productId๋‚˜ referrer์ด ๋ณ€ํ•˜์ง€ ์•Š์œผ๋ฉด handleSubmit ์ž์ฒด๊ฐ€ ๋ณ€ํ•˜์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ShippingForm์„ ๋ฆฌ๋ Œ๋”๋งํ•˜์ง€ ์•Š๊ณ  handleSubmit ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค๋‹ˆ๋‹ค. ํ•จ์ˆ˜์˜ ์ฝ”๋“œ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ํผ์„ ์ œ์ถœํ•˜๊ธฐ ์ „๊นŒ์ง€ ์‹คํ–‰๋˜์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด๋ฏธ useMemo์— ์ต์ˆ™ํ•˜๋‹ค๋ฉด useCallback์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ƒ๊ฐํ•˜๋Š” ๊ฒƒ์ด ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// (React ๋‚ด๋ถ€์˜) ๊ฐ„๋‹จํ•œ ๊ตฌํ˜„
function useCallback(fn, dependencies) {
return useMemo(() => fn, dependencies);
}

useMemo์™€ useCallback์˜ ์ฐจ์ด์ ์— ๋Œ€ํ•ด ๋” ์•Œ์•„๋ณด์„ธ์š”.

Deep Dive

ํ•ญ์ƒ useCallback์„ ์‚ฌ์šฉํ•ด์•ผ ํ• ๊นŒ์š”?

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

useCallback์œผ๋กœ ํ•จ์ˆ˜๋ฅผ ์บ์‹ฑํ•˜๋Š” ๊ฒƒ์€ ๋ช‡ ๊ฐ€์ง€ ๊ฒฝ์šฐ์—๋งŒ ๊ฐ€์น˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • memo๋กœ ๊ฐ์‹ธ์ง„ ์ปดํฌ๋„ŒํŠธ์— prop์œผ๋กœ ๋„˜๊น๋‹ˆ๋‹ค. ์ด ๊ฐ’์ด ๋ณ€ํ•˜์ง€ ์•Š์œผ๋ฉด ๋ฆฌ๋ Œ๋”๋ง์„ ๊ฑด๋„ˆ๋›ฐ๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. memoization์€ ์˜์กด์„ฑ์ด ๋ณ€ํ–ˆ์„ ๋•Œ๋งŒ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ฆฌ๋ Œ๋”๋งํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  • ๋„˜๊ธด ํ•จ์ˆ˜๊ฐ€ ๋‚˜์ค‘์— ์–ด๋–ค Hook์˜ ์˜์กด์„ฑ์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, useCallback์œผ๋กœ ๊ฐ์‹ธ์ง„ ๋‹ค๋ฅธ ํ•จ์ˆ˜๊ฐ€ ์ด ํ•จ์ˆ˜์— ์˜์กดํ•˜๊ฑฐ๋‚˜, useEffect์—์„œ ์ด ํ•จ์ˆ˜์— ์˜์กดํ•ฉ๋‹ˆ๋‹ค.

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

useCallback์ด ํ•จ์ˆ˜์˜ ์ƒ์„ฑ์„ ๋ง‰์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ์„ ์ฃผ์˜ํ•˜์„ธ์š”. ํ•ญ์ƒ ํ•จ์ˆ˜๋ฅผ ์ƒ์„ฑํ•˜์ง€๋งŒ (์ด๊ฑด ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค!), ๊ทธ๋Ÿฌ๋‚˜ React๋Š” ๋ณ€๊ฒฝ์ด ์—†๋Š” ๊ฒฝ์šฐ์—๋Š” ๋ฌด์‹œํ•˜๊ณ  ์บ์‹œ๋œ ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค

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

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

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

useCallback๊ณผ ํ•จ์ˆ˜๋ฅผ ์ง์ ‘ ์„ ์–ธํ•˜๋Š” ๊ฒƒ์˜ ์ฐจ์ด์ 

Example 1 of 2:
useCallback๊ณผ memo๋กœ ๋ฆฌ๋ Œ๋”๋ง ๊ฑด๋„ˆ๋›ฐ๊ธฐ

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

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

๋‹ค์Œ์œผ๋กœ ํ…Œ๋งˆ๋ฅผ ํ† ๊ธ€ ํ•ด๋ณด์„ธ์š”. useCallback์„ memo์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•œ ๋•๋ถ„์—, ์ธ์œ„์ ์ธ ์ง€์—ฐ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ๋น ๋ฆ…๋‹ˆ๋‹ค! ShippingForm์€ handleSubmit ํ•จ์ˆ˜๊ฐ€ ๋ณ€ํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฆฌ๋ Œ๋”๋ง์„ ๊ฑด๋„ˆ๋›ฐ์—ˆ์Šต๋‹ˆ๋‹ค. productId ์™€ referrer (useCallback์˜ ์˜์กด์„ฑ) ๋ชจ๋‘ ๋งˆ์ง€๋ง‰ ๋ Œ๋”๋ง์œผ๋กœ๋ถ€ํ„ฐ ๋ณ€ํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— handleSubmit ํ•จ์ˆ˜๋„ ๋ณ€ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

import { useCallback } from 'react';
import ShippingForm from './ShippingForm.js';

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

  return (
    <div className={theme}>
      <ShippingForm onSubmit={handleSubmit} />
    </div>
  );
}

function post(url, data) {
  // ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค๊ณ  ์ƒ๊ฐํ•˜์„ธ์š”...
  console.log('POST /' + url);
  console.log(data);
}


Memoized ์ฝœ๋ฐฑ์—์„œ ์ƒํƒœ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ

๋•Œ๋•Œ๋กœ memoized ์ฝœ๋ฐฑ์—์„œ ์ด์ „ ์ƒํƒœ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•  ๋•Œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

handleAddTodo ํ•จ์ˆ˜๋Š” todos๋กœ๋ถ€ํ„ฐ ๋‹ค์Œ ํ•  ์ผ์„ ๊ณ„์‚ฐํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ์˜์กด์„ฑ์œผ๋กœ ๋ช…์‹œํ–ˆ์Šต๋‹ˆ๋‹ค.

function TodoList() {
const [todos, setTodos] = useState([]);

const handleAddTodo = useCallback((text) => {
const newTodo = { id: nextId++, text };
setTodos([...todos, newTodo]);
}, [todos]);
// ...

๋ณดํ†ต์€ memoized ํ•จ์ˆ˜๊ฐ€ ๊ฐ€๋Šฅํ•œ ํ•œ ์ ์€ ์˜์กด์„ฑ์„ ๊ฐ–๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ์ƒํƒœ๋ฅผ ๊ณ„์‚ฐํ•˜๊ธฐ ์œ„ํ•ด ์–ด๋–ค ์ƒํƒœ๋ฅผ ์ฝ๋Š” ๊ฒฝ์šฐ, ์—…๋ฐ์ดํŠธ ํ•จ์ˆ˜๋ฅผ ๋Œ€์‹  ๋„˜๊ฒจ์คŒ์œผ๋กœ์จ ์˜์กด์„ฑ์„ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

function TodoList() {
const [todos, setTodos] = useState([]);

const handleAddTodo = useCallback((text) => {
const newTodo = { id: nextId++, text };
setTodos(todos => [...todos, newTodo]);
}, []); // โœ… todos ์˜์กด์„ฑ์€ ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
// ...

์—ฌ๊ธฐ์„œ todos๋ฅผ ์˜์กด์„ฑ์œผ๋กœ ๋งŒ๋“ค๊ณ  ์•ˆ์—์„œ ๊ฐ’์„ ์ฝ๋Š” ๋Œ€์‹ , React์— ์–ด๋–ป๊ฒŒ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ• ์ง€์— ๋Œ€ํ•œ ์ง€์นจ์„ ๋„˜๊ฒจ์ค๋‹ˆ๋‹ค. ์—…๋ฐ์ดํŠธ ํ•จ์ˆ˜์— ๋Œ€ํ•ด ๋” ์•Œ์•„๋ณด์„ธ์š”.


Effect๊ฐ€ ๋„ˆ๋ฌด ์ž์ฃผ ์‹คํ–‰๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ

๊ฐ€๋” Effect ์•ˆ์—์„œ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

function createOptions() {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}

useEffect(() => {
const options = createOptions();
const connection = createConnection();
connection.connect();
// ...

์ด๊ฒƒ์€ ๋ฌธ์ œ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. ๋ชจ๋“  ๋ฐ˜์‘ํ˜• ๊ฐ’์€ Effect์˜ ์˜์กด์„ฑ์œผ๋กœ ์„ ์–ธ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ createOptions๋ฅผ ์˜์กด์„ฑ์œผ๋กœ ์„ ์–ธํ•˜๋ฉด Effect๊ฐ€ ์ฑ„ํŒ…๋ฐฉ๊ณผ ๊ณ„์† ์žฌ์—ฐ๊ฒฐ๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

useEffect(() => {
const options = createOptions();
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // ๐Ÿ”ด ๋ฌธ์ œ์ : ์ด ์˜์กด์„ฑ์€ ๋งค ๋ Œ๋”๋ง๋งˆ๋‹ค ๋ณ€๊ฒฝ๋ฉ๋‹ˆ๋‹ค.
// ...

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด, Effect์—์„œ ํ˜ธ์ถœํ•˜๋ ค๋Š” ํ•จ์ˆ˜๋ฅผ useCallback์œผ๋กœ ๊ฐ์Œ€ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

const createOptions = useCallback(() => {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}, [roomId]); // โœ… roomId๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งŒ ๋ณ€๊ฒฝ๋ฉ๋‹ˆ๋‹ค.

useEffect(() => {
const options = createOptions();
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // โœ… createOptions๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งŒ ๋ณ€๊ฒฝ๋ฉ๋‹ˆ๋‹ค.
// ...

์ด๊ฒƒ์€ ๋ฆฌ๋ Œ๋”๋ง ๊ฐ„์— roomId๊ฐ€ ๊ฐ™๋‹ค๋ฉด createOptions ํ•จ์ˆ˜๋Š” ๊ฐ™๋‹ค๋Š” ๊ฒƒ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ํ•จ์ˆ˜ ์˜์กด์„ฑ์„ ์ œ๊ฑฐํ•˜๋Š” ๊ฒƒ์ด ๋” ์ข‹์Šต๋‹ˆ๋‹ค. ํ•จ์ˆ˜๋ฅผ Effect ์•ˆ์œผ๋กœ ์ด๋™์‹œํ‚ค์„ธ์š”.

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

useEffect(() => {
function createOptions() { // โœ… useCallback์ด๋‚˜ ํ•จ์ˆ˜ ์˜์กด์„ฑ์ด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}

const options = createOptions();
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [roomId]); // โœ… roomId๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งŒ ๋ณ€๊ฒฝ๋ฉ๋‹ˆ๋‹ค.
// ...

์ด์ œ ์ฝ”๋“œ๋Š” ๋” ๊ฐ„๋‹จํ•ด์กŒ๊ณ  useCallback์€ ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Effect์˜ ์˜์กด์„ฑ ์ œ๊ฑฐ์— ๋Œ€ํ•ด ๋” ์•Œ์•„๋ณด์„ธ์š”.


์ปค์Šคํ…€ Hook ์ตœ์ ํ™”ํ•˜๊ธฐ

์ปค์Šคํ…€ Hook์„ ์ž‘์„ฑํ•˜๋Š” ๊ฒฝ์šฐ, ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ชจ๋“  ํ•จ์ˆ˜๋ฅผ useCallback์œผ๋กœ ๊ฐ์‹ธ๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

function useRouter() {
const { dispatch } = useContext(RouterStateContext);

const navigate = useCallback((url) => {
dispatch({ type: 'navigate', url });
}, [dispatch]);

const goBack = useCallback(() => {
dispatch({ type: 'back' });
}, [dispatch]);

return {
navigate,
goBack,
};
}

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด Hook์„ ์‚ฌ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ•„์š”ํ•  ๋•Œ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์ฝ”๋“œ๋ฅผ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๋ฌธ์ œ ํ•ด๊ฒฐ

์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง ๋  ๋•Œ๋งˆ๋‹ค useCallback์ด ๋‹ค๋ฅธ ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

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

์˜์กด์„ฑ ๋ฐฐ์—ด์„ ๊นŒ๋จน์œผ๋ฉด useCallback์€ ๋งค๋ฒˆ ์ƒˆ๋กœ์šด ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

function ProductPage({ productId, referrer }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}); // ๐Ÿ”ด ๋งค๋ฒˆ ์ƒˆ๋กœ์šด ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค: ์˜์กด์„ฑ ๋ฐฐ์—ด ์—†์Œ
// ...

๋‹ค์Œ์€ ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์˜์กด์„ฑ ๋ฐฐ์—ด์„ ๋„˜๊ฒจ์ฃผ๋„๋ก ์ˆ˜์ •ํ•œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

function ProductPage({ productId, referrer }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]); // โœ… ๋ถˆํ•„์š”ํ•˜๊ฒŒ ์ƒˆ๋กœ์šด ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
// ...

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

const handleSubmit = useCallback((orderDetails) => {
// ..
}, [productId, referrer]);

console.log([productId, referrer]);

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

Object.is(temp1[0], temp2[0]); // ์ฒซ ๋ฒˆ์งธ ์˜์กด์„ฑ์ด ๋ฐฐ์—ด ๊ฐ„์— ๋™์ผํ•œ๊ฐ€์š”?
Object.is(temp1[1], temp2[1]); // ๋‘ ๋ฒˆ์งธ ์˜์กด์„ฑ์ด ๋ฐฐ์—ด ๊ฐ„์— ๋™์ผํ•œ๊ฐ€์š”?
Object.is(temp1[2], temp2[2]); // ... ๋‚˜๋จธ์ง€ ๋ชจ๋“  ์˜์กด์„ฑ๋„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค ...

์–ด๋–ค ์˜์กด์„ฑ์ด memoization์„ ๊นจ๊ณ  ์žˆ๋Š”์ง€ ์ฐพ์•˜๋‹ค๋ฉด ์ด๋ฅผ ์ œ๊ฑฐํ•˜๊ฑฐ๋‚˜ memoizationํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ฐพ์œผ์„ธ์š”.


๋ฐ˜๋ณต๋ฌธ์—์„œ ๊ฐ ํ•ญ๋ชฉ๋งˆ๋‹ค useCallback์„ ํ˜ธ์ถœํ•˜๊ณ  ์‹ถ์ง€๋งŒ, ์ด๊ฒƒ์€ ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

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

function ReportList({ items }) {
return (
<article>
{items.map(item => {
// ๐Ÿ”ด ์ด๋ ‡๊ฒŒ ๋ฐ˜๋ณต๋ฌธ ์•ˆ์—์„œ useCallback์„ ํ˜ธ์ถœํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
const handleClick = useCallback(() => {
sendReport(item)
}, [item]);

return (
<figure key={item.id}>
<Chart onClick={handleClick} />
</figure>
);
})}
</article>
);
}

๋Œ€์‹  ๊ฐœ๋ณ„ ํ•ญ๋ชฉ์„ ์ปดํฌ๋„ŒํŠธ๋กœ ๋ถ„๋ฆฌํ•˜๊ณ , ๊ฑฐ๊ธฐ์— useCallback์„ ๋„ฃ์œผ์„ธ์š”.

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

function Report({ item }) {
// โœ… useCallback์„ ์ตœ์ƒ์œ„ ๋ ˆ๋ฒจ์—์„œ ํ˜ธ์ถœํ•˜์„ธ์š”
const handleClick = useCallback(() => {
sendReport(item)
}, [item]);

return (
<figure>
<Chart onClick={handleClick} />
</figure>
);
}

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

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

const Report = memo(function Report({ item }) {
function handleClick() {
sendReport(item);
}

return (
<figure>
<Chart onClick={handleClick} />
</figure>
);
});