React : Hook
React : 컴포넌트와 Props, State (+ useState 훅) 화면을 각 요소로 쪼갠 것- 하나의 JSX 를 반환하는 함수 컴포넌트 만들기 - PascalCase를 사용하여 컴포넌트를 만든다.- 컴포넌트는 기본" data-og-host="jini-dev.t
jini-dev.tistory.com
useCallback
컴포넌트에서 최초 렌더링 시 특정 함수를 기억했다가 리렌더링 할 때 재활용 하는 훅이다.
- 메모이제이션 하고 싶은 함수를 콜백 함수로 작성한다.
=> 리렌더링이 발생해도 내부의 함수는 여러번 생성하지 않는다.
- 의존성 배열(dependency list)의 값에 따라 useCallback 내부 로직의 실행 조건을 적용 할 수 있다.
1. 의존성 배열이 빈 경우 : 최초 컴포넌트 렌더링 시 한번만 실행(함수를 저장)한다.
2. 의존성 배열에 값이 들어간 경우 : 들어간 상태의 값이 변경 될 때에만 실행 한다.
(값이 여러개 인경우 여러 상태 값 중 하나라도 변경될 때마다 함수를 지우고 재생성한다)
=> 메모이제이션 했던 함수를 지우고 현재 상태로 함수를 재생성한다.
useCallback 의 사용
모든 함수에 useCallback을 남용한다면 오히려 불필요한 메모이제이션으로 코드가 복잡해지고, 성능이 저하될 수 있다.
아래의 경우(함수 객체의 참조 동일성이 중요한 상황 등)가 아니라면 굳이 사용할 필요가 없다.
1. 함수를 자식 컴포넌트에 props로 전달할 때
: 부모 컴포넌트가 리렌더링 될 때 함수가 새로 생성되면 자식 컴포넌트의 props가 변경된 것으로 인식되어 불필요하게 자식 컴포넌트가 리렌더링 된다
2. React.memo, useMemo 등과 함께 렌더링 최적화가 필요할 때
3. useEffect, useMemo 등 의존성 배열에 함수를 넣어야 할 때
: 의존성 배열에 함수를 넣어야 하는 경우 useCallback으로 불필요한 재실행을 막을 수 있다.
4. 이벤트 핸들러 함수가 자주 재생성되어 성능에 영향을 줄 때
메모이제이션(Memoization)?
특정 부하가 생기는 함수를 반복해서 작업할 때, 함수 호출의 결과를 저장해두었다가
동일한 입력이 들어오면 다시 계산하지 않고 저장된 결과를 반복하여 사용하는 최적화 기법이다.
대표적인 메모이제이션 도구는 아래와 같다.
- React.memo : 컴포넌트의 props가 변하지 않으면 이전 렌더링 결과를 재사용해서 불필요한 렌더링을 막아준다.
- useMemo : 복잡한 계산의 결과 값을 저장해두고, 의존성이 바뀌지 않는 한 재계산하지 않는다.
- useCallback : 함수 자체를 메모이제이션하여, 의존성이 바뀌지 않으면 같은 함수 객체를 재사용한다.
메모이제이션 하는 이유?
컴포넌트가 리렌더링 될 때마다 함수를 계속 만든다.
이전에 생성한 함수는 브라우저에서 자동으로 Garbage Collecting을 해주지만 그래도 불필요하게 똑같은 역할을 하는 함수를 계속 생성한다.
=> 이러한 코드가 많을 수록 성능이 떨어지기 때문에 메모이제이션을 사용해서 성능 최적화에 도움을 줄 수 있다.
import React, { useCallback } from 'react';
export default function ComponentName() {
const handleClickButton = useCallback(() => {
console.log('버튼을 클릭 했습니다.');
}, []); // 빈 배열 : 최초 1회만 함수 저장
return <button onClick={handleClickButton}>버튼</button>;
}
버튼 3번 클릭
❗useCallback 은 왜 개발자 모드에서도 콘솔이 한번만 출력될까? (useEffect 에서는 두번씩 출력됐는데..)
React.StrictMode여도 useCallback으로 생성된 함수는 의존성 배열이 변하지 않는 한 최초 한 번만 생성된다.
=> 컴포넌트가 여러번 마운트/언마운트 되어도 새로 생성하지 않고, 이전에 생성된 함수의 객체가 재사용되기 때문에
useState나 useEffect 등에서 처럼 콘솔이 두번씩 출력되지 않는다.
사용 예시
import React, { useState, useCallback } from 'react';
export default function UseCallbackComponent() {
// 1. 의존성 배열이 [] 빈 배열 인 경우
const handleClickButton = useCallback(() => {
console.log('버튼을 클릭 했습니다.');
}, []); // 의존성 배열 : [] 빈 배열 => 최초 렌더링 시 1회만 함수를 저장하고 그대로 사용한다.
// 2. 의존성 배열에 값이 들어간 경우
const [value, setValue] = useState(0);
const handleIncreaseValue = useCallback(() => {
console.log('value 값을 증가합니다.');
setValue(value + 1);
}, [value]); // 의존성 배열 : [value] => value 값이 변경 될 때마다 기존 함수를 지우고 재생성한다.
return (
<>
<button onClick={handleClickButton}>버튼</button>
<h1>value : {value}</h1>
<button onClick={handleIncreaseValue}>증가버튼</button>
</>
);
}
의존성 배열에 따른 useCallback
import React, { useState, useCallback } from 'react';
export default function ComponentName() {
// 1. 의존성 배열이 [] 빈 배열 인 경우
const handleIncreaseValue1 = useCallback(() => {
setValue(value + 1);
console.log('빈배열 버튼 :', value);
}, []);
// 2. 의존성 배열에 값이 들어간 경우
const [value, setValue] = useState(0);
const handleIncreaseValue2 = useCallback(() => {
setValue(value + 1);
console.log('value 배열 버튼 :', value);
}, [value]);
return (
<>
<h1>value : {value}</h1>
<button onClick={handleIncreaseValue1}>빈배열버튼</button>
<button onClick={handleIncreaseValue2}>value배열버튼</button>
</>
);
}
위와 같이 코드를 짜보았다
1. 빈배열 버튼을 5번 눌러보았다
값이 1이 증가한 상태로 계속 머물러 있다
????
설명
useCallback의 의존성 배열이 [ ] 빈 배열이기 때문에,
useCallback의 내부 함수는 컴포넌트가 처음 렌더링(마운트) 될 때 한 번만 생성되었다.
이때 함수 내부에서 참조하는 value의 값도 마운트 시점의 value( 즉, value의 초기 값인 0) 으로 고정되었기 때문에
value가 아무리 바뀌어도 이 함수 내부에서 참조하는 value 의 값은 0으로 변하지 않는 것이다.
(즉, 버튼을 아무리 눌러도 함수 내부 value는 항상 0 이므로 setValue(0+1) 만 반복해서 실행되는 것)
ex)
value : 0 -> 버튼 클릭 -> setValue(1), console.log(0) -> 리렌더링 -> 화면 value : 1
value : 0 -> 버튼 클릭 -> setValue(1), console.log(0) -> 리렌더링 -> 화면 value : 1
콘솔 값이 계속 0인 이유는 아래(2번) 참고
2. value 배열 버튼을 5번 눌러보았다
값이 원하는대로 증가했다.
handleIncreaseValue2 는 value 가 바뀔 때 마다 새로 만들어졌다. => value 값이 원하는대로 증가했다.
근데 왜 콘솔에 출력된 value 값은 4인가
??????????
설명
useState의 setter 함수는 비동기적으로 동작해서,
setter함수를 호출하면 컴포넌트가 리렌더링 될 때 state(상태) 값이 변경되어 반영된다.
EX) setValue를 호출 한 후 변경한 값이 컴포넌트가 리렌더링 될 때 value에 반영된다.
따라서 setValue(value + 1)을 호출하면 value 값이 즉시 바뀌지 않고, 다음 렌더링 때 반영되기 때문에
setValue 호출 직후 console.log를 출력 하면 변경되기 이전의 value 값을 출력하는 것이다.
=> 아직 렌더링 되기 이전에 setValue 호출 후 value 값을 콘솔로 출력했기 때문에 value가 증가하기 이전의 값을 출력함
ex)
value : 0 버튼 클릭 -> setValue(1), console.log(0) -> 리렌더링 -> 화면 value : 1
value : 1 버튼 클릭 -> setValue(2),console.log(1) -> 리렌더링 -> 화면 value : 2
위와 같은 문제를 해결하고 싶다면 코드를 이런식으로 작성해서 확인하면 된다.
useState의 함수형 업데이트
1번의 문제를 해결해보자
=> useCallback 에 의존성 배열을 빈 배열로 넣었더니 value값이 초기 값으로 고정되어 변경되지도, 화면에서 바뀌지도 않는다.
const handleIncreaseValue1 = useCallback(() => {
setValue((prev) => prev + 1); // setter 함수의 함수형 업데이트를 사용 => prev : 기존 value의 값
console.log('버튼 클릭 :', value);
}, []);
useCallback 내부의 value는 초기 값 0으로 고정되어, 콘솔에서는 0으로 찍히지만
실제 value 값이 변경되었기 때문에 화면 상의 value는 변경되었다.
2번 문제를 해결해보자
=> 콘솔로그가 변경되는 화면과 동일하게 나오게 하고 싶다.
const handleIncreaseValue2 = useCallback(() => {
setValue((prev) => { // setter 함수의 함수형 업데이트를 사용 => prev : 기존 value의 값
// 기존 value에 + 1 값을 next 변수로 지정하여 콘솔로 출력하고 setValue 변경 값으로 반환한다
const next = prev + 1;
console.log('value 배열 버튼 :', next);
return next;
});
}, [value]);
❗콘솔에는 바로 원하는 값이 나오게 했지만,
useState의 함수형 업데이트를 사용 하는 경우에는 의존성 배열에 값을 ([value]) 넣을 필요가 없다!!!
이건 왜 콘솔에 두번 씩 찍히는가?
❗setValue 에서 updater 함수( 함수형 업데이트)에 적용한 콘솔은 StrictMode 에서 두 번씩 실행된다.
- useCallback 함수 내부 콘솔은 React.StrictMode 에서 한 번 실행
- useState의 updater 함수 (setter 함수의 함수형 업데이트) 내부 콘솔은 React.StrictMode 에서 두 번 실행
근데 위 1번 해결 방법 함수를 useCallback 을 사용하는거랑 useCallback 을 사용하지 않는 코드가 성능 차이가 크게 많이 나나??
-> 이벤트 핸들러로만 쓸때는 성능상 거의 차이 없다고 함
그리고 위 2번 해결 방법과 같이 value가 변경될 때마다 함수가 지워지고 재생성되면 useCallback 사용 안해도 되는 것 아닌가???
(물론 2번 해결 방식과 같이 쓰면 안된다! 함수형 업데이트를 하는 경우 의존성 배열에 값을 넣을 필요가 없음)
오히려 useCallback을 남용하면 메모이제이션 비용이 발생해 성능이 떨어질 수 있다.
그래서 보통은 아래와 같은 경우에 useCallback을 사용한다.
useCallback을 사용하는 경우
import React, { useState, useCallback } from 'react';
export default function UseCallbackComponent() {
const [value, setValue] = useState(0);
const increaseValue = () => {
setValue(value + 1);
console.log('증가 버튼 클릭');
};
const resetValue = useCallback(() => {
setValue(0);
console.log('리셋 버튼 클릭');
}, []);
return (
<>
<h1>value : {value}</h1>
<button onClick={increaseValue}>증가 버튼</button>
<button onClick={resetValue}>리셋 버튼</button>
</>
);
}
increaseValue 함수는useCallback 을 사용해도 value 값이 변경될 때마다 함수를 재생성하기 때문에 useCallback 이 불필요하다.
resetValue와 같은 함수에 (어떤 값이 와도 value를 0으로 만들어서 재활용성이 좋은 함수) useCallback을 사용하면 최초 렌더링 후 같은 함수를 재활용해서 쓰기 때문에 좀 더 좋은 성능으로 컴포넌트를 실행할 것이다.
'💻Dev > React' 카테고리의 다른 글
React : 아코디언 컴포넌트 기능 구현해보기 (1) | 2025.05.13 |
---|---|
React : Lifecycle(라이프 사이클) - 클래스형 컴포넌트 vs 함수형 컴포넌트 (0) | 2025.05.13 |
React : 기본 훅(Hook) - useEffect ( + 개발자 모드에서 콘솔이 두 번 씩 찍히는 이유 : React.StrictMode) (0) | 2025.05.13 |
React : 기본 훅(Hook) - useState (0) | 2025.05.13 |
React : Hook (0) | 2025.05.10 |