0Pricing
React Native Academy · 강의

React.memo, useCallback, useMemo를 사용한 메모이제이션

비용이 큰 자식 컴포넌트를 React.memo로 감싸고 useCallback으로 콜백 prop을 안정화하며 useMemo로 파생 값을 캐시하여 불필요한 렌더링을 방지합니다.

React.memo, useCallback, useMemo를 사용한 메모이제이션은(는) CoddyKit의 무료 React Native Academy 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 React Native Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. React Native Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.

Why Components Re-Render

In React, a component re-renders whenever its parent re-renders, its state changes, or its context changes. When a parent re-renders, all child components re-render by default — even if their props have not changed. For a list with 50 items, this means 50 re-renders on every parent state change.

Memoization is the technique of caching the result of a computation and returning the cached result when the inputs have not changed. React provides three memoization tools: React.memo, useCallback, and useMemo.

React.memo for Component Memoization

React.memo is a higher-order component that wraps a functional component and prevents re-renders when the component's props have not changed (using shallow equality comparison). If the parent re-renders but passes the same props, the memoized child is skipped.

Wrap a component with React.memo when it receives stable props and is expensive to render. Row components in a FlatList are the classic use case — they receive the same item data on every parent scroll event.

import React from 'react';
import { View, Text } from 'react-native';

interface PostRowProps {
  title: string;
  author: string;
}

// Without memo: re-renders on every parent update
// function PostRow({ title, author }: PostRowProps) {

// With memo: skips re-render if title and author are unchanged
const PostRow = React.memo(function PostRow({ title, author }: PostRowProps) {
  return (
    <View>
      <Text>{title}</Text>
      <Text>{author}</Text>
    </View>
  );
});

export default PostRow;

React.memo Custom Comparison

By default, React.memo uses a shallow comparison — it checks if each top-level prop is strictly equal (===). For objects and arrays, shallow equality means the reference must be the same, not just the content.

If your component receives an object prop and you want to compare by value rather than reference, pass a custom comparison function as the second argument to React.memo. The function receives the previous and next props and returns true if the component should not re-render.

const PostRow = React.memo(
  function PostRow({ post }) {
    return <Text>{post.title}</Text>;
  },
  // Custom comparison: skip re-render if post.id and post.title are the same
  (prevProps, nextProps) => {
    return (
      prevProps.post.id === nextProps.post.id &&
      prevProps.post.title === nextProps.post.title
    );
  }
);

useCallback: Stabilizing Function References

When a parent component defines a function inside its render body, that function is recreated on every render, producing a new reference each time. If that function is passed as a prop to a React.memo child, the memo is defeated because the prop reference always changes.

useCallback(fn, deps) memoizes the function reference and only creates a new function when the values in the dependency array change. This keeps the prop reference stable across renders, allowing React.memo to work correctly.

import { useCallback } from 'react';

export function PostList() {
  const [posts, setPosts] = useState([]);

  // Without useCallback: new function reference on every render
  // const handleDelete = (id) => deletePost(id);

  // With useCallback: stable reference, memoized child is not re-rendered
  const handleDelete = useCallback((id: string) => {
    deletePost(id);
    setPosts((prev) => prev.filter((p) => p.id !== id));
  }, []); // No deps: function never changes

  return (
    <FlatList
      data={posts}
      renderItem={({ item }) => (
        <PostRow post={item} onDelete={handleDelete} />
      )}
    />
  );
}

useMemo: Caching Expensive Computations

useMemo(fn, deps) memoizes the return value of a function. React runs the function once, caches the result, and returns the cached value on subsequent renders until the dependency array values change.

Use useMemo for expensive operations like sorting or filtering large arrays, transforming API data, or computing derived values from state. Do not use it for cheap computations — the memoization overhead can exceed the benefit.

import { useMemo } from 'react';

export function ContactList({ contacts, searchQuery }) {
  // Recomputed only when contacts or searchQuery changes
  const filteredContacts = useMemo(() => {
    if (!searchQuery) return contacts;
    const query = searchQuery.toLowerCase();
    return contacts.filter((c) =>
      c.name.toLowerCase().includes(query) ||
      c.email.toLowerCase().includes(query)
    );
  }, [contacts, searchQuery]);

  return (
    <FlatList
      data={filteredContacts}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => <ContactRow contact={item} />}
    />
  );
}

Dependency Arrays: The Key to Correctness

Both useCallback and useMemo rely on dependency arrays to know when to recompute. Every variable from the outer scope that is used inside the memoized function must be listed in the dependency array — otherwise the cached value will be stale.

The ESLint plugin eslint-plugin-react-hooks enforces this rule automatically. The exhaustive-deps rule warns if you omit a dependency. Missing deps are the most common source of subtle bugs with memoization.

// WRONG: userId is used inside but not in deps
const fetchPosts = useCallback(async () => {
  const posts = await api.getPostsByUser(userId); // stale userId!
  setPosts(posts);
}, []); // Missing dependency: userId

// CORRECT: all used external values listed
const fetchPosts = useCallback(async () => {
  const posts = await api.getPostsByUser(userId);
  setPosts(posts);
}, [userId]); // Recomputed when userId changes

Memoizing Context Values

When you provide a Context value, React compares the value reference on every render. If the context value is an object literal created inline, it gets a new reference every render, causing all context consumers to re-render even if no data actually changed.

Wrap the context value in useMemo to stabilize its reference. Split context into separate providers when possible — for example, keep user data and UI theme in separate contexts so a theme change does not re-render all components that consume user data.

const ThemeContext = createContext(null);

export function ThemeProvider({ children }) {
  const [isDark, setIsDark] = useState(false);

  // Without useMemo: new object reference on every render
  // value={{ isDark, toggleTheme: () => setIsDark(d => !d) }}

  // With useMemo: stable reference, consumers only re-render when isDark changes
  const value = useMemo(() => ({
    isDark,
    toggleTheme: () => setIsDark((d) => !d),
  }), [isDark]);

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}

When NOT to Memoize

Memoization is not free. useMemo and useCallback run the comparison on every render. For simple components and cheap operations, this overhead can make performance worse, not better.

Avoid memoization when: the component renders infrequently, the computation is trivially fast (like adding two numbers), or the dependencies change on every render (making the memo useless). Apply memoization only where the React Profiler shows a measurable improvement.

// ❌ Over-memoization: computation is trivial, no benefit
const fullName = useMemo(() => {
  return firstName + ' ' + lastName;
}, [firstName, lastName]);

// ✅ Better: just compute it
const fullName = firstName + ' ' + lastName;

// ✅ Good use of useMemo: sorting 5000 items is expensive
const sortedItems = useMemo(() => {
  return [...items].sort(compareByDate);
}, [items]);

The useCallback Pattern for FlatList renderItem

FlatList re-renders item rows when its renderItem prop changes. Defining renderItem as an inline arrow function creates a new reference on every parent render, defeating any memoization on the item component.

Define renderItem with useCallback and add only the dependencies that truly change its behavior. Also memoize the keyExtractor function for the same reason — stable references allow FlatList to optimize its reconciliation.

const renderItem = useCallback(
  ({ item }) => <PostRow post={item} onDelete={handleDelete} />,
  [handleDelete] // handleDelete is already wrapped in useCallback
);

const keyExtractor = useCallback(
  (item) => item.id,
  [] // Pure function, no deps
);

return (
  <FlatList
    data={posts}
    renderItem={renderItem}
    keyExtractor={keyExtractor}
  />
);

Using the Profiler to Verify Memoization Works

After applying memoization, use the React Profiler to verify it is actually working. Record the same interaction and compare the flame graph before and after. Memoized components should appear grayed out in the profiler (indicating they were skipped) rather than colored bars (indicating they rendered).

If a memoized component still shows up as rendered, check the Why did this render? panel in DevTools. It will tell you exactly which prop or hook changed, revealing an unstable reference or a missing memoization step.

Summary: When to Use Each Tool

A quick decision guide for React memoization tools:

  • React.memo — wrap a component whose parent re-renders often but whose own props change rarely
  • useCallback — stabilize a function reference passed as a prop to a memoized child, or used as a dependency of another hook
  • useMemo — cache an expensive derived value (filtering, sorting, transforming large arrays) that is recomputed on every render

Always profile first to confirm there is a real problem, apply one optimization at a time, and measure again to confirm the improvement.

Quick Check

Test your understanding of React Native Mobile Development concepts from this lesson.

Lesson Recap

In this lesson you learned: how React.memo prevents child re-renders when props have not changed, how useCallback stabilizes function references to make React.memo effective, and how useMemo caches expensive computed values across renders. Next up we tune FlatList rendering performance for smooth list scrolling.

자주 묻는 질문

“React.memo, useCallback, useMemo를 사용한 메모이제이션” 강의는 무료인가요?

네 — “React.memo, useCallback, useMemo를 사용한 메모이제이션” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 React Native Academy 강의 전체를 잠금 해제할 수 있습니다. React Native Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“React.memo, useCallback, useMemo를 사용한 메모이제이션”에서 뭘 배우나요?

비용이 큰 자식 컴포넌트를 React.memo로 감싸고 useCallback으로 콜백 prop을 안정화하며 useMemo로 파생 값을 캐시하여 불필요한 렌더링을 방지합니다. 브라우저에서 직접 실행하는 실습 코드로 React Native Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

React Native Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 React Native Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.

“React.memo, useCallback, useMemo를 사용한 메모이제이션” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 React Native Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 React Native Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. Flipper 및 React DevTools로 프로파일링하기
  2. React.memo, useCallback, useMemo를 사용한 메모이제이션
  3. FlatList 성능 조정
  4. 번들 크기 및 지연 로딩
← React Native Academy(으)로 돌아가기