[TIL] useRef와 lazy initializer

2026. 06. 12.Yeji Kim
Frontend

문제

이런 코드가 있다고 하자.

tsx
const storedSizeRef = useRef(getStoredSize());

storedSizeRef.current는 첫 렌더의 값으로 유지된다.

그런데 getStoredSize() 자체는 컴포넌트가 렌더될 때마다 다시 호출된다.

처음에는 조금 이상해 보인다.

컴포넌트는 매 렌더마다 다시 실행된다

React 함수 컴포넌트는 말 그대로 함수다.

리렌더링이 발생하면 React는 컴포넌트 함수를 다시 호출한다.

tsx
function Component() {
  const storedSizeRef = useRef(getStoredSize());
 
  return null;
}

그래서 렌더가 발생할 때마다 함수 본문의 코드도 다시 실행된다.

함수의 인자는 먼저 계산된다

JavaScript는 함수를 호출하기 전에 인자를 먼저 평가한다.

tsx
useRef(getStoredSize());

이 코드는 실행 순서상 다음과 비슷하다.

tsx
const value = getStoredSize();
useRef(value);

즉 React의 useRef가 실행되기 전에 이미 getStoredSize()가 호출된 상태다.

React가 초기값을 무시할 수는 있어도, 이미 발생한 함수 호출까지 막을 수는 없다.

useRef가 유지하는 것은 값이 아니라 ref 객체다

첫 렌더에서 useRef는 대략 이런 객체를 만든다.

tsx
{
  current: initialValue
}

이후 렌더에서는 새 객체를 만들지 않고 이전 ref 객체를 그대로 반환한다.

tsx
// 첫 렌더
const value = getStoredSize();
const ref = { current: value };
 
// 이후 렌더
const unusedValue = getStoredSize();
return previousRef;

따라서 결과는 다음과 같다.

text
getStoredSize() 호출: 매 렌더 발생
storedSizeRef.current: 첫 렌더의 값 유지

useState의 lazy initializer

tsx
const [storedSize] = useState(() => getStoredSize());

여기서는 getStoredSize()의 결과가 아니라 함수 자체를 React에 전달한다.

tsx
const initializer = () => getStoredSize();
 
useState(initializer);

React가 이 initializer를 마운트할 때만 실행하기 때문에, getStoredSize()도 초기 렌더에서만 호출된다.

반면 아래 코드는 lazy initializer가 아니다.

tsx
useState(getStoredSize());

이 경우에도 함수 인자가 먼저 계산되므로 getStoredSize()는 매 렌더마다 호출된다.

useRef를 한 번만 초기화하려면

tsx
const storedSizeRef = useRef<Size | null>(null);
 
if (storedSizeRef.current === null) {
  storedSizeRef.current = getStoredSize();
}

이렇게 하면 실제 값이 없을 때만 getStoredSize()가 실행된다.

값이 UI 렌더링에 사용되고, 변경 시 화면도 다시 그려야 한다면 useState가 더 자연스럽다.

tsx
const [storedSize] = useState(() => getStoredSize());

정리

useRef(getStoredSize())에서 함수가 반복 호출되는 이유는 useRef의 특수한 동작 때문이라기보다 JavaScript의 함수 인자 평가 순서 때문이다.

tsx
const storedSizeRef = useRef(getStoredSize());

React가 받는 시점에는 이미 getStoredSize()가 실행된 뒤다.

useRef는 이후 렌더에서 새 초기값을 사용하지 않을 뿐, 초기값을 계산하는 표현식까지 실행하지 않게 해주지는 않는다.