useCallback
is a React Hook that lets you cache a function definition between re-renders.
useCallback
은 리렌더링 사이에 함수 정의를 캐시할 수 있게 해주는 React 훅입니다.
const cachedFn = useCallback(fn, dependencies)
Reference
useCallback(fn, dependencies)
Call useCallback
at the top level of your component to cache a function definition between re-renders:
최상위 컴포넌트에서 useCallback
을 호출하여 리렌더링 사이에 함수 정의를 캐시합니다:
import { useCallback } from 'react';
export default function ProductPage({ productId, referrer, theme }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
See more examples below. 아래에서 더 많은 예시를 확인하세요.
Parameters매개변수
-
fn
: The function value that you want to cache. It can take any arguments and return any values. React will return (not call!) your function back to you during the initial render. On next renders, React will give you the same function again if thedependencies
have not changed since the last render. Otherwise, it will give you the function that you have passed during the current render, and store it in case it can be reused later. React will not call your function. The function is returned to you so you can decide when and whether to call it.fn
: 캐시하려는 함수 값입니다. 어떤 인자도 받을 수 있고 어떤 값이라도 반환할 수 있습니다. React는 초기 렌더링을 하는 동안 함수를 반환합니다(호출하지 않습니다!). 다음 렌더링에서 React는 마지막 렌더링 이후dependencies
가 변경되지 않았다면 동일한 함수를 다시 제공합니다. 그렇지 않으면 현재 렌더링 중에 전달한 함수를 제공하고 나중에 재사용할 수 있도록 저장합니다. React는 함수를 호출하지 않습니다. 함수는 반환되므로 호출 시기와 여부를 결정할 수 있습니다. -
dependencies
: The list of all reactive values referenced inside of thefn
code. Reactive values include props, state, and all the variables and functions declared directly inside your component body. If your linter is configured for React, it will verify that every reactive value is correctly specified as a dependency. The list of dependencies must have a constant number of items and be written inline like[dep1, dep2, dep3]
. React will compare each dependency with its previous value using theObject.is
comparison algorithm.dependencies
:fn
코드 내에서 참조된 모든 반응형 값의 배열입니다. 반응형 값에는 props, state, 컴포넌트 본문 내부에서 직접 선언한 모든 변수 및 함수가 포함됩니다. 린터가 React용으로 구성된 경우, 모든 반응형 값이 의존성으로 올바르게 지정되었는지 확인합니다. 의존성 배열에는 일정한 수의 항목이 있어야 하며[dep1, dep2, dep3]
과 같이 인라인으로 작성해야 합니다. React는Object.is
비교 알고리즘을 사용하여 각 의존성을 이전 값과 비교합니다.
Returns반환 값
On the initial render, useCallback
returns the fn
function you have passed.
초기 렌더링에서 useCallback
은 전달한 fn
함수를 반환합니다.
During subsequent renders, it will either return an already stored fn
function from the last render (if the dependencies haven’t changed), or return the fn
function you have passed during this render.
렌더링 중에는 마지막 렌더링에서 이미 저장된 fn
함수를 반환하거나(의존성이 변경되지 않은 경우), 렌더링 중에 전달했던 fn
함수를 반환합니다.
Caveats주의 사항
-
useCallback
is a Hook, so you can only call it at the top level of your component or your own Hooks. You can’t call it inside loops or conditions. If you need that, extract a new component and move the state into it.useCallback
은 훅이므로 컴포넌트의 최상위 레벨이나 자체 훅에서만 호출할 수 있습니다. 반복문이나 조건문 내부에서는 호출할 수 없습니다. 필요한 경우 새로운 컴포넌트로 추출하고 state를 그 안으로 옮기세요. -
React will not throw away the cached function unless there is a specific reason to do that. For example, in development, React throws away the cache when you edit the file of your component. Both in development and in production, React will throw away the cache if your component suspends during the initial mount. In the future, React may add more features that take advantage of throwing away the cache—for example, if React adds built-in support for virtualized lists in the future, it would make sense to throw away the cache for items that scroll out of the virtualized table viewport. This should match your expectations if you rely on
useCallback
as a performance optimization. Otherwise, a state variable or a ref may be more appropriate. React는 특별한 이유가 없는 한 캐시된 함수를 버리지 않습니다. 예를 들어, 개발 단계에서 컴포넌트의 파일을 수정할 때 React는 캐시를 버립니다. React는 개발 중이든 생산 중이든 초기 마운트 중에 컴포넌트가 일시 중단되면 캐시를 버립니다. 향후 React는 캐시를 버리는 것의 이점을 취하는 더 많은 기능을 추가할 수 있습니다. 예를 들어, 향후 React에 가상화된 목록에 대한 빌트인 지원이 추가되면 가상화된 테이블 뷰포트에서 스크롤되는 항목에 대한 캐시도 버리는 것도 이해가 될 것입니다. 성능 최적화를 위해useCallback
에 의존하는 경우 기대에 부합할 것입니다. 그렇지 않은 경우 state 변수나 ref가 더 적합할 수 있습니다.
Usage사용법
Skipping re-rendering of components컴포넌트 리렌더링 건너뛰기
When you optimize rendering performance, you will sometimes need to cache the functions that you pass to child components. Let’s first look at the syntax for how to do this, and then see in which cases it’s useful. 렌더링 성능을 최적화할 때 자식 컴포넌트에 전달하는 함수를 캐시해야 할 때가 있습니다. 먼저 이를 수행하는 방법에 대한 구문을 살펴본 다음 어떤 경우에 유용한지 알아보겠습니다.
To cache a function between re-renders of your component, wrap its definition into the useCallback
Hook:
컴포넌트의 리렌더링 사이에 함수를 캐시하려면, 해당 함수의 정의를 useCallback
훅으로 감싸세요:
import { useCallback } from 'react';
function ProductPage({ productId, referrer, theme }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
// ...
You need to pass two things to useCallback
:
useCallback
을 사용하려면 두 가지를 전달해야 합니다:
-
A function definition that you want to cache between re-renders. 리렌더링 사이에 캐시할 함수
-
A list of dependencies including every value within your component that’s used inside your function. 함수 내에서 사용되는 컴포넌트 내부의 모든 값을 포함하는 의존성 배열
On the initial render, the returned function you’ll get from useCallback
will be the function you passed.
초기 렌더링 시에는 useCallback
에서 반환되는 함수는 처음에 전달했던 함수입니다.
On the following renders, React will compare the dependencies with the dependencies you passed during the previous render. If none of the dependencies have changed (compared with Object.is
), useCallback
will return the same function as before. Otherwise, useCallback
will return the function you passed on this render.
다음 렌더링부터는, React는 이전 렌더링에서 전달된 의존성과 비교합니다. 만약 의존성 중 변경된 것이 없다면(Object.is로 비교), useCallback
은 이전과 같은 함수를 반환합니다. 그렇지 않으면 useCallback
은 이번 렌더링에서 전달한 함수를 반환합니다.
In other words, useCallback
caches a function between re-renders until its dependencies change.
즉, useCallback
은 의존성이 변경되기 전까지는 리렌더링에 대해 함수를 캐시합니다.
Let’s walk through an example to see when this is useful. useCallback이 언제 유용한지 예시를 통해 살펴보겠습니다.
Say you’re passing a handleSubmit
function down from the ProductPage
to the ShippingForm
component:
ProductPage
에서 ShippingForm
컴포넌트로 handleSubmit
함수를 전달한다고 가정해 보겠습니다:
function ProductPage({ productId, referrer, theme }) {
// ...
return (
<div className={theme}>
<ShippingForm onSubmit={handleSubmit} />
</div>
);
You’ve noticed that toggling the theme
prop freezes the app for a moment, but if you remove <ShippingForm />
from your JSX, it feels fast. This tells you that it’s worth trying to optimize the ShippingForm
component.
theme
prop을 토글하면 앱이 잠시 멈추는 것을 알아차렸겠지만 JSX에서 <ShippingForm />
을 제거하면 빠르게 느껴집니다. 이는 ShippingForm
컴포넌트를 최적화할 가치가 있다는 것을 알려줍니다.
By default, when a component re-renders, React re-renders all of its children recursively. This is why, when ProductPage
re-renders with a different theme
, the ShippingForm
component also re-renders. This is fine for components that don’t require much calculation to re-render. But if you verified a re-render is slow, you can tell ShippingForm
to skip re-rendering when its props are the same as on last render by wrapping it in memo
:
기본적으로 컴포넌트가 리렌더링되면 React는 모든 자식들을 재귀적으로 리렌더링합니다. 이는 ProductPage가 다른 theme
로 리렌더링될 때 ShippingForm
컴포넌트도 리렌더링되기 때문입니다. 이는 리렌더링하는 데 많은 계산이 필요하지 않은 컴포넌트에는 괜찮습니다. 그러나 리렌더링이 느리다는 것을 확인했다면, props가 지난 렌더링과 동일한 경우 memo
로 감싸 ShippingForm
에게 리렌더링을 건너뛰도록 지시할 수 있습니다:
import { memo } from 'react';
const ShippingForm = memo(function ShippingForm({ onSubmit }) {
// ...
});
With this change, ShippingForm
will skip re-rendering if all of its props are the same as on the last render. This is when caching a function becomes important! Let’s say you defined handleSubmit
without useCallback
:
이 변경으로 ShippingForm
은 모든 props가 마지막 렌더링과 동일한 경우 리렌더링을 건너뜁니다. 바로 이 때 함수 캐싱이 중요해집니다! useCallback
없이 handleSubmit
을 정의했다고 가정해 봅시다:
function ProductPage({ productId, referrer, theme }) {
// Every time the theme changes, this will be a different function...
// 테마가 변경될 때마다, 이 함수는 달라집니다...
function handleSubmit(orderDetails) {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}
return (
<div className={theme}>
{/* ... so ShippingForm's props will never be the same, and it will re-render every time */}
{/*따라서 ShippingForm의 props는 절대 같지 않으며, 매번 리렌더링 됩니다.*/}
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}
In JavaScript, a function () {}
or () => {}
always creates a different function, similar to how the {}
object literal always creates a new object. Normally, this wouldn’t be a problem, but it means that ShippingForm
props will never be the same, and your memo
optimization won’t work. This is where useCallback
comes in handy:
JavaScript에서 function () {}
또는 () => {}
는 {}
객체 리터럴이 항상 새 객체를 생성하는 것과 유사하게 항상 다른 함수를 생성합니다. 일반적으로는 문제가 되지 않지만 ShippingForm
의 props는 결코 동일하지 않으며 memo
최적화가 작동하지 않는다는 의미입니다. 바로 이 지점에서 useCallback
이 유용합니다:
function ProductPage({ productId, referrer, theme }) {
// Tell React to cache your function between re-renders...
// 리렌더링 사이에 함수를 캐싱하도록 지시합니다...
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]); // ...so as long as these dependencies don't change...
// ...따라서 이 의존성이 변경되지 않는 한...
return (
<div className={theme}>
{/* ...ShippingForm will receive the same props and can skip re-rendering */}
{/* ...ShippingForm은 동일한 props를 받으므로 리렌더링을 건너뛸 수 있습니다.*/}
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}
By wrapping handleSubmit
in useCallback
, you ensure that it’s the same function between the re-renders (until dependencies change). You don’t have to wrap a function in useCallback
unless you do it for some specific reason. In this example, the reason is that you pass it to a component wrapped in memo
, and this lets it skip re-rendering. There are other reasons you might need useCallback
which are described further on this page.
handleSubmit
을 useCallback
으로 감싸면, 리렌더링 사이에 동일한 함수가 되도록 할 수 있습니다(의존성이 변경될때까지). 특별한 이유가 없는 한 함수를 useCallback
으로 감쌀 필요는 없습니다. memo
로 감싼 컴포넌트에 함수를 전달하면 이로 인해 리렌더링을 건너뛸 수 있게 되기 때문입니다. useCallback
이 필요한 다른 이유들은 이 페이지 하단에 더 자세히 설명되어 있습니다.
Deep Dive | 심층 탐구
You will often see useMemo
alongside useCallback
. They are both useful when you’re trying to optimize a child component. They let you memoize (or, in other words, cache) something you’re passing down:
useMemo
와 함께 useCallback
이 자주 사용되는 것을 볼 수 있습니다. 자식 컴포넌트를 최적화하려고 할 때, 두 가지 모두 유용합니다. 전달하는 값을 memoize(캐시)할 수 있게 해줍니다.
import { useMemo, useCallback } from 'react';
function ProductPage({ productId, referrer }) {
const product = useData('/product/' + productId);
const requirements = useMemo(() => { // Calls your function and caches its result
// 함수를 호출하고 그 결과를 캐시합니다.
return computeRequirements(product);
}, [product]);
const handleSubmit = useCallback((orderDetails) => { // Caches your function itself
// 함수 자체를 캐시합니다.
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
return (
<div className={theme}>
<ShippingForm requirements={requirements} onSubmit={handleSubmit} />
</div>
);
}
The difference is in what they’re letting you cache: 차이점은 캐시할 수 있는 항목에 있습니다:
useMemo
caches the result of calling your function. In this example, it caches the result of callingcomputeRequirements(product)
so that it doesn’t change unlessproduct
has changed. This lets you pass therequirements
object down without unnecessarily re-renderingShippingForm
. When necessary, React will call the function you’ve passed during rendering to calculate the result.useMemo
는 호출한 함수의 결과를 캐시합니다. 이 예제에서는product
가 변경되지 않는 한 변경되지 않도록computeRequirements(product)
를 호출한 결과를 캐시합니다. 이렇게 하면 불필요하게ShippingForm
을 리렌더링하지 않고도requirements
객체를 전달할 수 있습니다. 필요한 경우, React는 렌더링 중에 전달된 함수를 호출하여 결과를 계산합니다.useCallback
caches the function itself. UnlikeuseMemo
, it does not call the function you provide. Instead, it caches the function you provided so thathandleSubmit
itself doesn’t change unlessproductId
orreferrer
has changed. This lets you pass thehandleSubmit
function down without unnecessarily re-renderingShippingForm
. Your code won’t run until the user submits the form.useCallback
은 함수 자체를 캐시합니다.useMemo
와 달리, 제공한 함수를 호출하지 않습니다. 대신 제공한 함수를 캐시하여productId
또는referrer
가 변경되지 않는 한handleSubmit
자체가 변경되지 않도록 합니다. 이렇게 하면 불필요하게ShippingForm
을 리렌더링하지 않고도handleSubmit
함수를 전달할 수 있습니다. 사용자가 폼을 제출할 때까지 코드가 실행되지 않습니다.
If you’re already familiar with useMemo
, you might find it helpful to think of useCallback
as this:
useMemo
에 이미 익숙하다면 useCallback
을 이렇게 생각하면 도움이 될 수 있습니다:
// Simplified implementation (inside React)
// 간소화된 구현체 (React 내부)
function useCallback(fn, dependencies) {
return useMemo(() => fn, dependencies);
}
Read more about the difference between useMemo
and useCallback
.
useMemo
와 useCallback
의 차이점에 대해 자세히 알아보세요.
Deep Dive | 심층 탐구
If your app is like this site, and most interactions are coarse (like replacing a page or an entire section), memoization is usually unnecessary. On the other hand, if your app is more like a drawing editor, and most interactions are granular (like moving shapes), then you might find memoization very helpful. 이 사이트와 같이 대부분의 인터랙션이 투박한 앱의 경우(페이지 또는 전체 섹션 교체 등) 일반적으로 메모화는 필요하지 않습니다. 반면 앱이 그림 편집기 처럼 대부분의 인터랙션이 도형 이동 처럼 세분화되어 있다면 메모화가 매우 유용할 수 있습니다.
Caching a function with useCallback
is only valuable in a few cases:
useCallback
으로 함수를 캐싱하는 것은 몇 가지 경우에만 유용합니다:
-
You pass it as a prop to a component wrapped in
memo
. You want to skip re-rendering if the value hasn’t changed. Memoization lets your component re-render only if dependencies changed.memo
로 감싼 컴포넌트에 prop으로 전달합니다. 값이 변경되지 않았다면 렌더링을 건너뛰고 싶을 것입니다. 메모화를 사용하면 의존성이 변경된 경우에만 컴포넌트를 리렌더링할 수 있습니다. -
The function you’re passing is later used as a dependency of some Hook. For example, another function wrapped in
useCallback
depends on it, or you depend on this function fromuseEffect.
전달한 함수는 나중에 일부 훅의 의존성으로 사용됩니다. 예를 들어,useCallback
으로 감싼 다른 함수가 이 함수에 의존하거나,useEffect
에서 이 함수에 의존할 수 있습니다.
There is no benefit to wrapping a function in useCallback
in other cases. There is no significant harm to doing that either, so some teams choose to not think about individual cases, and memoize as much as possible. The downside is that code becomes less readable. Also, not all memoization is effective: a single value that’s “always new” is enough to break memoization for an entire component.
다른 경우에는 함수를 useCallback
으로 감싸는 것이 이득이 없습니다. 그렇게 한다고 해서 크게 해가 되는 것도 아니기 때문에 일부 팀에서는 개별 사례에 대해 생각하지 않고 가능한 한 많이 메모하는 방식을 선택하기도 합니다. 단점은 코드 가독성이 떨어진다는 것입니다. 또한 모든 메모화가 효과적인 것은 아닙니다. “항상 새로운” 단일 값만으로도 전체 컴포넌트에 대한 메모화가 깨질 수 있습니다.
Note that useCallback
does not prevent creating the function. You’re always creating a function (and that’s fine!), but React ignores it and gives you back a cached function if nothing changed.
useCallback
이 함수 생성을 막지 않는다는 것을 기억하세요. 여러분은 항상 함수를 생성하지만(그리고 그것은 괜찮습니다!), React는 변경된 것이 없다면 이를 무시하고 캐시된 함수를 반환합니다.
In practice, you can make a lot of memoization unnecessary by following a few principles: 사실, 다음 몇 가지 원칙을 따르면 많은 메모화를 불필요하게 만들 수 있습니다:
-
When a component visually wraps other components, let it accept JSX as children. Then, if the wrapper component updates its own state, React knows that its children don’t need to re-render. 컴포넌트가 다른 컴포넌트를 시각적으로 감쌀 때 JSX를 자식으로 받아들이도록 하세요. 그러면 래퍼 컴포넌트가 자신의 state를 업데이트 하더라도 React는 자식 컴포넌트가 리렌더링할 필요가 없다는 것을 알 수 있습니다.
-
Prefer local state and don’t lift state up any further than necessary. Don’t keep transient state like forms and whether an item is hovered at the top of your tree or in a global state library. 로컬 state를 선호하고 필요 이상으로 state를 끌어올리지 마세요. 트리 상단 또는 전역 상태 라이브러리에 폼(form)이나 아이템(item)의 호버(hover) 상태와 같은 일시적인 state를 유지하지 마세요.
-
Keep your rendering logic pure. If re-rendering a component causes a problem or produces some noticeable visual artifact, it’s a bug in your component! Fix the bug instead of adding memoization. 렌더링 로직을 순수하게 유지하세요. 만약 컴포넌트를 리렌더링했을 때 문제가 발생하거나 눈에 띄는 시각적 아티팩트가 생성된다면 컴포넌트에 버그가 있는 것입니다! 메모화를 추가하는 대신 버그를 수정하세요.
-
Avoid unnecessary Effects that update state. Most performance problems in React apps are caused by chains of updates originating from Effects that cause your components to render over and over. state를 업데이트하는 불필요한 Effect를 피하세요. React 앱에서 대부분의 성능 문제는 컴포넌트를 반복적으로 렌더링하게 하는 Effect의 업데이트 체인으로 인해 발생합니다.
-
Try to remove unnecessary dependencies from your Effects. For example, instead of memoization, it’s often simpler to move some object or a function inside an Effect or outside the component. Effect에서 불필요한 의존성을 제거하세요. 예를 들어, 메모화 대신 일부 겍체나 함수를 Effect 내부나 컴포넌트 외부로 이동하는 것이 더 간단할 때가 많습니다.
If a specific interaction still feels laggy, use the React Developer Tools profiler to see which components benefit the most from memoization, and add memoization where needed. These principles make your components easier to debug and understand, so it’s good to follow them in any case. In long term, we’re researching doing memoization automatically to solve this once and for all. 특정 인터랙션이 여전히 느리게 느껴진다면 React 개발자 도구 프로파일러를 사용해 어떤 컴포넌트가 메모화의 이점을 가장 많이 누리는지 확인하고, 필요한 경우 메모화를 추가하세요. 이러한 원칙은 컴포넌트를 더 쉽게 디버깅하고 이해할 수 있게 해주므로 어떤 경우든 이 원칙을 따르는 것이 좋습니다. 장기적으로는 이 문제를 완전히 해결하기 위해 메모화를 자동으로 수행하는 방법을 연구하고 있습니다.
Example 1 of 2: Skipping re-rendering with useCallback
and memo
useCallback
과 memo
로 리렌더링 건너뛰기
In this example, the ShippingForm
component is artificially slowed down so that you can see what happens when a React component you’re rendering is genuinely slow. Try incrementing the counter and toggling the theme.
이 예시에서는 렌더링 중인 React 컴포넌트가 실제로 느릴 때 어떤 일이 일어나는지 확인할 수 있도록 ShippingForm
컴포넌트의 속도를 인위적으로 느리게 설정했습니다. 카운터를 증가시키고 테마를 토글해 보세요.
Incrementing the counter feels slow because it forces the slowed down ShippingForm
to re-render. That’s expected because the counter has changed, and so you need to reflect the user’s new choice on the screen.
카운터를 증가시키면 느려진 ShippingForm
을 리렌더링하느라 느리게 느껴집니다. 카운터가 변경되어 사용자의 새로운 선택 사항을 화면에 반영해야 하므로 예상 가능한 현상입니다.
Next, try toggling the theme. Thanks to useCallback
together with memo
, it’s fast despite the artificial slowdown! ShippingForm
skipped re-rendering because the handleSubmit
function has not changed. The handleSubmit
function has not changed because both productId
and referrer
(your useCallback
dependencies) haven’t changed since last render.
다음으로 테마를 전환해 보세요. useCallback
와 memo
덕분에 인위적인 속도 저하에도 불구하고 빨라졌습니다! handleSumbmit
함수가 변경되지 않았기 때문에 ShippingForm
이 리렌더링을 건너뛰었습니다. 지난 렌더링 이후 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) { // Imagine this sends a request... console.log('POST /' + url); console.log(data); }
Updating state from a memoized callback메모된 콜백에서 state 업데이트하기
Sometimes, you might need to update state based on previous state from a memoized callback. 때로는 메모된 콜백의 이전 state를 기반으로 state를 업데이트해야 할 수도 있습니다.
This handleAddTodo
function specifies todos
as a dependency because it computes the next todos from it:
이 handleAddTodo
함수는 다음 할일을 계산하기 위해 todos
를 의존성으로 지정하였습니다:
function TodoList() {
const [todos, setTodos] = useState([]);
const handleAddTodo = useCallback((text) => {
const newTodo = { id: nextId++, text };
setTodos([...todos, newTodo]);
}, [todos]);
// ...
You’ll usually want memoized functions to have as few dependencies as possible. When you read some state only to calculate the next state, you can remove that dependency by passing an updater function instead: 일반적으로 메모화된 함수는 가능한 적은 의존성을 갖기를 원할 것입니다. 다음 state를 계산하기 위해 일부 state만 읽어야 하는 경우, 대신 업데이터 함수를 전달하여 해당 의존성을 제거할 수 있습니다:
function TodoList() {
const [todos, setTodos] = useState([]);
const handleAddTodo = useCallback((text) => {
const newTodo = { id: nextId++, text };
setTodos(todos => [...todos, newTodo]);
}, []); // ✅ No need for the todos dependency
// ✅ todos에 대한 의존성이 필요하지 않음
// ...
Here, instead of making todos
a dependency and reading it inside, you pass an instruction about how to update the state (todos => [...todos, newTodo]
) to React. Read more about updater functions.
여기서는 todos
를 의존성으로 만들고 내부에서 읽는 대신, state를 업데이트하는 방법에 대한 지시사항(todos => [...todos, newTodo]
)을 React에 전달합니다. 업데이터 함수에 대해 자세히 읽어보세요.
Preventing an Effect from firing too oftenEffect가 너무 자주 발동되지 않도록 하기
Sometimes, you might want to call a function from inside an 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();
// ...
This creates a problem. Every reactive value must be declared as a dependency of your Effect. However, if you declare createOptions
as a dependency, it will cause your Effect to constantly reconnect to the chat room:
이로 인해 문제가 발생합니다. 모든 반응형 값은 Effect의 의존성으로 선언해야 합니다. 그러나 createOptions
를 의존성으로 선언하면 Effect가 채팅방에 계속 재연결하게 됩니다:
useEffect(() => {
const options = createOptions();
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // 🔴 Problem: This dependency changes on every render
// 🔴 문제: 이 의존성은 렌더링시마다 변경됨
// ...
To solve this, you can wrap the function you need to call from an Effect into useCallback
:
이 문제를 해결하려면 Effect에서 호출해야 하는 함수를 useCallback
으로 감싸면 됩니다:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
const createOptions = useCallback(() => {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}, [roomId]); // ✅ Only changes when roomId changes
// ✅ roomId 변경시에만 변경됨
useEffect(() => {
const options = createOptions();
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // ✅ Only changes when createOptions changes
// ✅ createOptions 변경시에만 변경됨
// ...
This ensures that the createOptions
function is the same between re-renders if the roomId
is the same. However, it’s even better to remove the need for a function dependency. Move your function inside the Effect:
이렇게 하면 roomId
가 동일한 경우 리렌더링 사이에 createOptions
함수가 동일하게 적용됩니다. 하지만 함수 의존성을 없애는 편이 더 좋습니다. 함수를 Effect 내부로 이동하세요:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
useEffect(() => {
function createOptions() { // ✅ No need for useCallback or function dependencies!
// ✅ useCallback이나 함수에 대한 의존성이 필요하지 않음!
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}
const options = createOptions();
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ Only changes when roomId changes
// ✅ roomId 변경시에만 변경됨
// ...
Now your code is simpler and doesn’t need useCallback
. Learn more about removing Effect dependencies.
이제 코드가 더 간단해졌으며 useCallback
이 필요하지 않습니다. Effect 의존성 제거에 대해 자세히 알아보세요.
Optimizing a custom Hook커스텀 훅 최적화하기
If you’re writing a custom Hook, it’s recommended to wrap any functions that it returns into useCallback
:
커스텀 훅을 작성하는 경우 반환하는 모든 함수를 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,
};
}
This ensures that the consumers of your Hook can optimize their own code when needed. 이렇게 하면 훅의 소비자가 필요할 때 자신의 코드를 최적화할 수 있습니다.
Troubleshooting문제 해결
Every time my component renders, useCallback
returns a different functionuseCallback
은 컴포넌트가 렌더링될 때마다 다른 함수를 반환합니다
Make sure you’ve specified the dependency array as a second argument! 두 번째 인자로 의존성 배열을 지정했는지 확인하세요!
If you forget the dependency array, useCallback
will return a new function every time:
의존성 배열을 잊어버린 경우 useCallback
은 매번 새로운 함수를 반환합니다:
function ProductPage({ productId, referrer }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}); // 🔴 Returns a new function every time: no dependency array
// 🔴 매 번 새 함수를 반환함: 의존성 배열이 없음
// ...
This is the corrected version passing the dependency array as a second argument: 다음은 의존성 배열을 두 번째 인수로 전달하는 수정된 버전입니다:
function ProductPage({ productId, referrer }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]); // ✅ Does not return a new function unnecessarily
// ✅ 불필요하게 새 함수를 반환하지 않음
// ...
If this doesn’t help, then the problem is that at least one of your dependencies is different from the previous render. You can debug this problem by manually logging your dependencies to the console: 그래도 도움이 되지 않는다면 의존성 중 하나 이상이 이전 렌더링과 다르기 때문일 수 있습니다. 의존성을 콘솔에 수동으로 로깅하여 이 문제를 디버그할 수 있습니다:
const handleSubmit = useCallback((orderDetails) => {
// ..
}, [productId, referrer]);
console.log([productId, referrer]);
You can then right-click on the arrays from different re-renders in the console and select “Store as a global variable” for both of them. Assuming the first one got saved as temp1
and the second one got saved as temp2
, you can then use the browser console to check whether each dependency in both arrays is the same:
그런 다음 콘솔에서 서로 다른 리렌더의 배열을 마우스 오른쪽 버튼으로 클릭하고 두 배열 모두에 대해 “전역 변수로 저장”을 선택하세요. 첫 번째 배열이 temp1
로 저장되고 두 번째 배열이 temp2
로 저장되었다고 가정하면 브라우저 콘솔을 사용하여 두 배열의 각 의존성이 동일한지 확인할 수 있습니다:
Object.is(temp1[0], temp2[0]); // Is the first dependency the same between the arrays?
// 각 배열의 첫번째 의존성이 동일한가?
Object.is(temp1[1], temp2[1]); // Is the second dependency the same between the arrays?
// 각 배열의 두번째 의존성이 동일한가?
Object.is(temp1[2], temp2[2]); // ... and so on for every dependency ...
// ... 나머지 모든 의존성에 대해 반복 ...
When you find which dependency is breaking memoization, either find a way to remove it, or memoize it as well. 어떤 의존성이 메모화를 방해하는지 찾았다면, 그 의존성을 제거할 방법을 찾거나 해당 의존성도 메모화하면 됩니다.
I need to call useCallback
for each list item in a loop, but it’s not allowed루프 안에서 목록의 각 항목에 대해 useCallback
를 호출하려 하는데, 허용되지 않는다고 합니다
Suppose the Chart
component is wrapped in memo
. You want to skip re-rendering every Chart
in the list when the ReportList
component re-renders. However, you can’t call useCallback
in a loop:
Chart
컴포넌트가 memo
로 감싸져 있다고 가정해 봅시다. ReportList
컴포넌트가 리렌더링할 때 목록의 모든 Chart
를 리렌더링하는 것을 건너뛰고 싶을 수 있습니다. 그러나 반복문에서는 useCallback
을 호출할 수 없습니다:
function ReportList({ items }) {
return (
<article>
{items.map(item => {
// 🔴 You can't call useCallback in a loop like this:
// 🔴 useCallback는 루프 안에서 호출할 수 없습니다:
const handleClick = useCallback(() => {
sendReport(item)
}, [item]);
return (
<figure key={item.id}>
<Chart onClick={handleClick} />
</figure>
);
})}
</article>
);
}
Instead, extract a component for an individual item, and put useCallback
there:
대신 개별 항목에 대한 컴포넌트를 추출하고 거기에 useCallback
을 넣으세요:
function ReportList({ items }) {
return (
<article>
{items.map(item =>
<Report key={item.id} item={item} />
)}
</article>
);
}
function Report({ item }) {
// ✅ Call useCallback at the top level:
// ✅ useCallback은 컴포넌트의 최상위 레벨에서 호출하세요:
const handleClick = useCallback(() => {
sendReport(item)
}, [item]);
return (
<figure>
<Chart onClick={handleClick} />
</figure>
);
}
Alternatively, you could remove useCallback
in the last snippet and instead wrap Report
itself in memo
. If the item
prop does not change, Report
will skip re-rendering, so Chart
will skip re-rendering too:
또는 마지막 스니펫에서 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>
);
});