React Hooks 중에서 useState() 나 useEffect() 처럼 많이 쓰이지는 않지만 가끔 나와서 햇갈리게 하는 녀석이 있습니다. 바로 useRef()
hook 함수인데요. 이번 포스팅에서는 useRef()
함수가 왜 필요하고, 언제 사용하는지에 대해서 알아보겠습니다.
React 컴포넌트는 기본적으로 내부 상태(state) 가 변할 때 마다 다시 렌더링(rendering)이 됩니다. 예를 들어, 아래 <Counter/>
컴포넌트의 버튼을 5번 클릭하면 count
상태값은 5번 바뀌게 됩니다.
import React, { useState } from 'react';
function Counter() {
const [ count, setCount ] = useState(0);
console.log(`렌더링 -> count: ${count}`);
return (
<>
<p>{count}번 클릭하셨습니다.</p>
<button onClick={() => setCount(count + 1)}>클릭</button>
</>
);
}
브라우저 콘솔을 확인해보면, 5번의 로그가 찍히는 것을 볼 수 있습니다. 이를 통해 <Counter/>
컴포넌트 함수는 count
상태가 바뀔 때 마다 호출되는 것을 알 수 있습니다.
렌더링 -> count: 1
렌더링 -> count: 2
렌더링 -> count: 3
렌더링 -> count: 4
렌더링 -> count: 5
컴포넌트 함수가 다시 호출이 된다는 것은 함수 내부의 변수들이 모두 다시 초기화가 되고 함수의 모든 로직이 다시 실행된다는 것을 의미합니다.
우리는 대부분의 경우, 위와 같이 상태가 변할 때 마다 React 컴포넌트 함수가 호출되어 화면이 갱신되기를 바랍니다.
하지만 그에 따른 부작용으로 함수 내부의 변수들이 기존에 저장하고 있는 값들을 잃어버리고 초기화되는데요. 간혹 다시 렌더링이 되더라도 기존에 참조하고 있던 컴포넌트 함수 내의 값이 그대로 보존되야 하는 경우가 있습니다.
예를 들어, 카운팅이 자동으로 되도록 useEffect
hook 함수를 이용하여 위의 컴포넌트를 수정해보겠습니다.
import React, { useState, useEffect } from 'react';
function AutoCounter() {
const [ count, setCount ] = useState(0);
console.log(`렌더링 -> count: ${count}`);
useEffect(() => {
const intervalId = setInterval(() => setCount((count) => count + 1), 1000);
return () => clearInterval(intervalId);
}, []);
// 첫 렌더링 이후 setInterval 함수로 인해 1초마다 계속 자동 카운트
// state 가 변경 되었으므로 리렌더링 된다.
// -> useEffect hook의 dependency 가 [] 이므로 첫 렌더링 에서만 함수가 호출된다.
// 그러므로 intervalId 는 리렌더링 되어도 동일한 참조값을 가진다.
return <p>자동 카운트: {count}</p>;
}
자, 여기서 만약에 카운트를 자동으로 시작하지 않고 버튼을 이용하여 시작하고 정지하고 싶다면 어떻게 해야 할까요?
import React, { useState, useEffect } from "react";
function ManualCounter() {
const [count, setCount] = useState(0);
let intervalId;
const startCounter = () => {
// 💥 매번 새로운 값 할당
intervalId = setInterval(() => setCount((count) => count + 1), 1000);
};
const stopCounter = () => {
clearInterval(intervalId);
};
return (
<>
<p>자동 카운트: {count}</p>
<button onClick={startCounter}>시작</button>
<button onClick={stopCounter}>정지</button>
</>
);
}
여기서 가장 큰 걸림돌은 안에서 선언된 intervalId
변수를 startCounter()
함수와 stopCounter()
함수가 공유할 수 있도록 해줘야 한다는 것입니다.