체인의정석

React) 재랜더링과 메모이제이션 Memo, useMemo & useEffect 본문

개발/frontend

React) 재랜더링과 메모이제이션 Memo, useMemo & useEffect

체인의정석 2025. 9. 25. 18:33
728x90

<재랜더링 패턴 3개>

React에서는 재랜더링이 발생하는 3가지 패턴

1. State 업데이트

2. Props가 변경된 컴포넌트

3. 재랜더링 된 컴포넌트 아래의 모든 컴포넌트

이러한 재랜더링을 계속하게 될 경우 부하가 많이 발생하게 된다.

<메모이제이션>

리액트에서 컴포넌트, 변수, 함수 등을 재렌더링할 때 제어가 필요한 경우에는 메모이제이션을 수행한다.

메모이제이션은 이전 처리 결과를 저장해 둠으로써 처리 속도를 높이는 기술로 필요할 때만 다시 계산하게 하여 불필요한 처리를 줄일 수 있다. 만약 부모가 재랜더링을 하더라도 메모이제이션이 되어 있다면 자녀 컴포넌트의 재랜더링을 막을 수 있다.

- 컴포넌트 메모이제이션

memo를 사용하면 컴포넌트 자체가 메모이제이션이 되면서 이러한 랜더링 과정에서 부모가 바뀌더라도 자식은 바뀌지 않게 된다.

- 함수 메모이제이션

그러나 함수를 Props로 받은 경우 자식 컴포넌트 입장에서는 Props가 변화했다고 판정해서 카운트업할 때마다 재랜더링을 하게 된다. 리액트에서는 함수 메모이제이션 기능인 UseCallBack을 제공하며 useCallBack의 경우 첫 번째 인수에 함수, 두번ㄷ째 인수에 useEffect와 같은 의존 배열을 받는다.

- 변수 메모이제이션

useMemo를 사용하면 변수 정의를 반복해서 하는 부하를 줄일 수 있다.  

<사용 패턴>

패턴 1: 존성이 없는 경우 (가장 일반적)

// ✅ 가장 일반적 - 의존성 배열이 비어있음
useEffect(() => {
    fetchData();
}, []); // 빈 배열로 마운트 시에만 실행

const fetchData = async () => {
    // API 호출
};

 

패턴 2: 의존성이 있는 경우

 
// ✅ 의존성이 있을 때는 useCallback 사용
const fetchData = useCallback(async () => {
    // API 호출
}, [userId, page]); // 의존성 명시

useEffect(() => {
    fetchData();
}, [fetchData]);

패턴 3: 직접 useEffect 내부에서 처리

// ✅ 간단한 경우 직접 처리
useEffect(() => {
    const fetchData = async () => {
        const res = await api.getData();
        setData(res);
    };
    fetchData();
}, [userId]); // 의존성만 명시
 

2. 언제 메모이제이션을 사용해야 할까? 🤔

useCallback 필요한 경우:

  • 함수가 여러 곳에서 용됨
  • 수가 다른 useEffect의 의존성으로 사용됨
  • 함수가 자식 컴포넌트에 props로 전달
  • 함수 내부에서 은 상태값을 참조함

useCallback이 불필요한 :

  • 함수가 useEffect 내부에서만 사용됨
  • 함수가 단순고 의존성이 적음
  • 컴포넌트가 자주 리렌더링되지 않음

3. 실제 예시 비교 📊

❌ 문제가 있는 코드:

const MyComponent = () => {
    const [data, setData] = useState([]);
    const [userId, setUserId] = useState(1);

    // 매번 새로운 함수가 생성됨
    const fetchData = async () => {
        const res = await api.getData(userId);
        setData(res);
    };

    // 무한 루프 발생!
    useEffect(() => {
        fetchData();
    }, [fetchData]); // fetchData가 매번 변경됨
};
 

 해결 방법 1: useCallback 

const MyComponent = () => {
    const [data, setData] = useState([]);
    const [userId, setUserId] = useState(1);

    const fetchData = useCallback(async () => {
        const res = await api.getData(userId);
        setData(res);
    }, [userId]); // userId가 변경될 때만 함수 재생성

    useEffect(() => {
        fetchData();
    }, [fetchData]);
};

 

✅ 해결 방법 2: 직접 처리 (더 간단)

const MyComponent = () => {
    const [data, setData] = useState([]);
    const [userId, setUserId] = useState(1);

    useEffect(() => {
        const fetchData = async () => {
            const res = await api.getData(userId);
            setData(res);
        };
        fetchData();
    }, [userId]); // userId가 변경될 때만 실행
};

4. 권장사항

간단한 경우 (권장):

useEffect(() => {
    const fetchData = async () => {
        // API 호출
    };
    fetchData();
}, [dependency]);
 

복잡한 경우:

 
const fetchData = useCallback(async () => {
    // 복잡한 로직
}, [dependencies]);

useEffect(() => {
    fetchData();
}, [fetchData]);

 

728x90
반응형
Comments