프론트엔드/React

useCallback()

bread-gee 2024. 1. 4. 20:23

🔥함수형 컴포넌트 기반으로 정리를 했습니다!

 

React.memo()는 함수형 컴포넌트를 메모이제이션하여 컴포넌트의 props가 변경되지 않으면 리렌더링을 하지 않도록 만들 수 있었다.

 

❓그렇다면 컴포넌트만큼 많이 생성되는 게 함수인데, 함수를 메모이제이션하는 방법은 없을까?

❗️있다! useCallback() 훅을 사용하여 함수를 메모이제이션 할 수 있다.

컴포넌트가 실행되는 동안 함수를 메모이제이션하고, 해당 함수를 불필요하게 재생성하지 않도록 도와준다.

함수가 불필요하게 재생성되지 않기 때문에 불필요한 렌더링을 방지하고 메모리 사용을 최적화할 수 있다.

 

더보기

🔥메모이제이션

컴퓨터 프로그램이 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술.

 

🌀useCallback()을 사용하면

  • 동일한 함수 객체가 동일한 메모리 위치에 저장되므로 함수를 비교해도 같다고 인식한다.
  • 재평가될 때 평가되는 함수는 항상 똑같은 위치의 함수를 참조한다.

 

 

 

📌 언제 사용할까?

🌀부모 컴포넌트가 자식 컴포넌트에 콜백 함수를 전달하는 경우

부모 컴포넌트가 리렌더링될 때마다 새로운 콜백 함수가 생성되는데, 콜백 함수의 변경이 없어도 자식 컴포넌트는 새로운 참조를 받아 리렌더링된다.

=> 부모 컴포넌트에서 생성된 콜백 함수를 메모이제이션하면, 부모 컴포넌트가 리렌더링될 때마다 동일한 콜백 함수 참조가 전달되어 자식 컴포넌트가 불필요하게 리렌더링되지 않는다.

const Parent = () => {
	const handleCallback = useCallback(() => {
    	// 콜백 함수 로직
	}, []); // 빈 의존성 배열로 전달된 경우, 콜백 함수는 컴포넌트 마운트 시에만 생성된다.

	return <ChildComponent callback={handleCallback} />;
};

const Child = ({ callback }) => {
  // 자식 컴포넌트에서 콜백 함수 사용
};

 

 

 

🌀의존성이 있는 Side effects를 수행하기 위해 사용되는 함수에서 사용하는 경우

useEffect처럼 Side effects를 수행하기 위해 사용되는 콜백 함수가 외부 변수에 의존하는 경우, 해당 변수가 변경될 때마다 매번 새로운 콜백 함수가 생성되어 효과 함수가 다시 실행된다.

=> useCallback을 사용하여 의존성 배열에 해당 변수를 명시하면, 해당 변수가 변경될 때만 메모이제이션된 함수가 새로 생성되어 효과 함수가 다시 실행된다.

const MyComponent = ({ data }) => {
	const handleEffect = useCallback(() => {
		// 의존성이 있는 효과 함수 로직
	}, [data]);
    
	useEffect(() => {
		handleEffect();
	}, [handleEffect]);
};

 

 

 

📌 사용 방법

  1. useCallback()으로 감싼다.
  2. 첫번째 인자로 함수를 전달한다.
  3. 두번째 인자로 의존성 배열 전달한다.
    useEffect 의존성 배열과 의미가 같다.
    >> 함수를 감싼 컴포넌트로부터 전달 받은 모든 것 사용 가능하다.
    >> 즉, 상태, props, 컨텍스트 지정할 수 있다.
// useCallback() 미사용
const toggleParagraphHandler = () => {
	setShowPara((prevShowPara) => !prevShowPara);
};

// useCallback() 사용
const toggleParagraphHandler = useCallback(() => {
	setShowPara((prevShowPara) => !prevShowPara);
}, [setShowPara]); // setShowPara는 절대 안바뀌니까 의존성 배열에 굳이 안넣어도 된다.

 

 

 

❓함수의 재생성을 막기 위해 useCallback()으로 함수를 메모이제이션했는데, 왜 의존성 배열이 필요할까?

❗️외부 변수 사용 시 외부 변수 변경함에 따라 함수 재생성을 필요로 하는 때가 있기 때문이다.

자바스크립트는 클로저를 생성하기 때문에, useCallback()을 이용해 함수 선언하면, 함수 생성 시점 기준으로 변수를 기억한다.

useCallback() 안에서 외부 변수를 사용한다면, 외부 변수의 상태가 변경되어도 함수 생성 시점 기준으로 외부 변수를 기억하기 때문에 함수가 원하는대로 동작하지 않는다.

그런 경우 외부 변수를 의존성 배열(선택적)에 추가시켜서 외부 변수가 변경될 경우에만 함수를 재생성하도록 한다.

더보기

🔥클로저

클로저(closure)는 함수와 그 함수가 선언된 렉시컬 스코프(lexical scope)에 있는 변수들을 함께 기억하는 특별한 객체이다.

클로저는 함수가 다른 스코프에서 호출될 때에도 그 함수가 생성될 당시의 환경을 기억하고, 그 환경의 변수에 접근할 수 있도록 한다.

 

🔥렉시컬 스코프

- 렉시컬 스코프는 변수의 유효 범위가 "코드를 작성(정의)하는 시점"에 결정되고, 함수가 어디에서 호출되는지와는 상관없이 어디에서 정의되었는지에 따라 결정된다.

- 자바스크립트는 렉시컬 스코프를 따르므로 함수를 어디에서 호출하든, 함수의 스코프는 함수가 정의된 위치에 의해 결정된다.

// App.js
function App() {
	const [showParagraph, setShowParagraph] = useState(false);
	const [allowToggle, setAllowToggle] = useState(false);

	const toggleParagraphHandler = useCallback(() => {
		// 외부 변수 allowToggle를 사용하는 경우
		if (allowToggle) {
			setShowParagraph((prevShowParagraph) => !prevShowParagraph);
		}
	}, []);

	const allowToggleHandler = () => {
		setAllowToggle(true);
	}
    
	return (
		<div className="app">
			<h1>Hi there!</h1>
			<DemoOutput show={showParagraph} />
			<Button onClick={toggleParagraphHandler}>Toggle Paragraph!</Button>
			<Button onClick={allowToggleHandler}>Allow Toggle</Button>
		</div>
	);
};

export default App;
// useCallback() 사용 시 의존성 배열 추가
const toggleParagraphHandler = useCallback(() => {
	if (allowToggle) {
		setShowParagraph((prevShowParagraph) => !prevShowParagraph);
	}
}, [allowToggle]); // 의존성 배열에 외부 변수 allowToggle 넣어줄 경우, allowToggle 상태값 변경 시 함수가 재생성된다.