결론: [의존성 배열] 난 너만 바라봐.
useCallback
useCallback(() => {
return value;
}, [의존성배열])
const calculate = useCallback((num) => {
return num + 1;
}, [의존성배열]
이렇게 함수를 useCallback으로 감싸주면 이제 이 calculate라는 변수는 메모이제이션된 함수를 갖고있게된다. 이는 의존성 배열 안에있는 값이 변경될 때 에만 초기화된다.
이 useCallback이 필요한 이유를 알고 이해하기 위해 상황을 설정해보자.
상황설정
간단한다. 인풋하나와 버튼하나가 있다.
- 인풋값은 number만 들어간다. 그리고 useState를 사용하여 변경값을 input value에 넣어준다.
- 버튼의 변화를 감지하여 콘솔에 '변경감지'를 출력하는 useEffect함수가 있다.
- 버튼에 달린 변수는 콘솔로그에 number를 찍는다.
import { useState, useEffect } from 'react';
function UseCallback() {
const [number, setNumber] = useState(0);
const someFunction = () => {
console.log(`someFunc: number: ${number}`);
return;
};
//버튼의 변화를 감지하여 콘솔에 '변경감지'를 출력하는 useEffect함수가 있다.
useEffect(() => {
console.log('변경감지');
}, [someFunction]);
return (
<div>
//인풋값은 number만 들어간다. 그리고 useState를 사용하여 변경값을 input value에 넣어준다.
<input
type='number'
value={number}
onChange={(e) => setNumber(e.target.value)}
/>
<br />
//버튼에 달린 변수는 콘솔로그에 number를 찍는다.
<button onClick={someFunction}>Call someFunc</button>
</div>
);
}
export default UseCallback;
예상하는 동작
: someFunction이 브라우저에 mount될 때, 그리고 someFunction에 변화가 있을 때 콘솔에 ‘변경감지' 가 출력
실제 결과
하지만 결과는 예상과는 다르게 someFunction에 아무런 변화가 없었음에도 콘솔에는 처음 mount 되었을 때 + setNumber함수가 실행되었을때에도 콘솔에 ‘변경감지'가 찍히는 것을 알 수 있다.
왜 그럴까?
두가지를 이해해야 한다.
- 리액트는 함수형 컴포넌트이다. 다시말해 랜더링이 일어나면 함수 안의 변수들은 초기화된다.
- 자바스크립트의 오브젝트는 reference data type이다.
리액트는 함수형 컴포넌트
지금 이 코드에서 input값에 변화를 줄 때마다 재랜더링이 된다. useState를 사용하고있기 때문이다.
useState는 값이 변화될 때 마다 랜더링을 일으켜 그 값을 업데이트한다. 그리고 랜더링은 useState가 적용되는 값만 렌더링되는게 아니라, 그 함수가 포함된 컴포넌트를 전체 랜더링시키고 그때마다 모든 변수들은 초기화된다.
근데 변수가 초기화되었다고 한들, someFunction에 변한게 없는데 왜 ‘변경감지'를 출력하냐고?
자바스크립트의 오브젝트는 reference data type이다.
그 말은 오브젝트가 어떤 변수에 할당이 될 때에는, 오브젝트 자체가 아니라 오브젝트는 어디 딴데에 가서 저장되어있고 그 저장된 ‘주소값'이 할당된다는 뜻이다.
위와 같이 똑같은 객체가 들어있음에도 불구하고 test1과 test2에는 각각 다른 주소값이 들어있기 때문에 비교시 false를 반환한다.
const someFunction = () => {
console.log(`someFunc: number: ${number}`);
return;
};
useEffect(() => {
console.log('변경감지');
}, [someFunction]);
이제 알겠는가? 즉 someFunction 안에는 객체가 들어있기 때문에, 브라우저가 재랜더링 되어 someFunction값이 초기화되면 ;그 전과는 다른 주소값’을 가지게 된다는 소리다!
당연히 useEffect의 입장에서는, someFunction의 주소값이 바뀌었으니 변화라고 인식하고 콘솔로그를 출력하는 것이다.
이를해결하기 위한 useCallback함수
// useCallback import
import { useState, useEffect, useCallback } from 'react';
function UseCallback() {
const [number, setNumber] = useState(0);
// 콜백함수 자체를 useCallback함수에 넣어준다.
const someFunction = useCallback(() => {
console.log(`someFunc: number: ${number}`);
return;
}, []);
//의존성 배열에 아무것도 넣어주지 않았으니 이 함수는 맨 처음 렌더링 될 때 만들어져서
//메모이제이션이 된다. someFunction안에는 메모이제이션 된 주소가 들어있다.
useEffect(() => {
console.log('변경감지');
}, [someFunction]);
return (
<div>
<input
type='number'
value={number}
onChange={(e) => setNumber(e.target.value)}
/>
<br />
<button onClick={someFunction}>Call someFunc</button>
</div>
);
}
export default UseCallback;
useCallback은 인자로 전달한 콜백함수 자체를 메모이제이션 해준다. 그 다음 랜더링부터는 변수가 재할당되지 않고 계속 처음에 메모이제이션 했던 값의 주소값을 사용한다. 의존성배열이 []이기 때문이다.
의존성 배열 안에 다른값을 넣으면 그 값이 변화될 때에 someFunction을 포함하여 랜더링된다. 여기서 넌 나만 바라봐가 나온것이다. 이름부터 의존성 배열이니까. 여친(남친)에게 의존하며 걔만 바라보고 있는거다. 걔가 변화되었을때만! 반응을 한다.
그런데 또 문제점이?
number를 변화시켜준 후 Call someFunc버튼을 눌러도 콘솔로그에 number는 0이 찍히는데, 이는 처음 렌더링에서 메모이제이션해줬을 당시에 number가 0이었기 때문에 그 값 그대로 메모이제이션 된 것이다.
이 경우 바로 전에 살펴봤던 것처럼 number가 변화될 때마다 someFunction 안의 함수를 갱신해주고싶으니 의존성 배열에 [number]를 넣으면 해결이다.
const someFunction = useCallback(() => {
console.log(`someFunc: number: ${number}`);
return;
}, [number]);
콘솔 파헤치기
- 처음 mount될 때 변경감지
- number가 바뀔때마다 useEffect가 someFunction의 변화를 감지하여 '변경감지'출력
- number가 바뀔때마다 useCallback이 실행되어 someFunction이 다시 초기화-> 콘솔에 number출력
'Front-End Developer > React' 카테고리의 다른 글
리액트 유치원이 개원했습니다. (유투브 채널개설) (0) | 2022.06.20 |
---|---|
useState Lazy initialization - 게으른 초기화가 대체 뭔소리야? (+유투브 영상 업로드) (4) | 2022.06.16 |
Router v6 헷갈려서 공부해봤다. 기초정리 (0) | 2022.06.14 |
React를 한다면 무조건 알아야 할 - useRef 1편 (0) | 2022.06.13 |
영혼없이 썼던 import React from 'react' - 안써도 된다는데? (0) | 2022.06.13 |