리액트를 사용하다 보면 컴포넌트의 상태를 관리하기 위해 useState를 많이 사용합니다. 하지만 초기 상태를 설정할 때 무거운 연산이 포함된 함수를 잘못 사용하면 성능 저하가 발생할 수 있습니다. 이를 해결하는 방법이 지연 초기화(Lazy Initialization) 입니다.
지연 초기화(Lazy Initialization)란, useState를 사용할 때 초기 상태를 설정하는 함수가 렌더링 시마다 불필요하게 실행되지 않도록 최적화하는 기법입니다.
function App() {
const [id, setId] = useState(veryHeavyComputation());
return <div>{id}</div>;
}
위 코드에서는 veryHeavyComputation()이 즉시 실행되어 useState로 함수의 실행 결과 값이 전달됩니다. 컴포넌트가 렌더링될 때마다 불필요한 연산이 반복되기 때문에 성능 낭비가 발생합니다.
리액트의 useState는 내부적으로 초기 상태를 설정할 때 전달된 값이 함수인지 확인하는 로직이 포함되어 있습니다.
function useState(initialState) {
let state;
// 🟢 전달된 값이 함수라면 실행하여 초기 상태 설정
if (typeof initialState === "function") {
state = initialState();
} else {
state = initialState;
}
function setState(newState) {
state = newState;
render(); // 컴포넌트 리렌더링
}
return [state, setState];
}
useState는 초기 상태를 함수로 전달받으면, 초기 렌더링 시에만 해당 함수를 실행합니다.
이후 setState를 호출해 상태를 변경해도 초기화 함수는 다시 실행되지 않습니다.
다음은 리액트가 상태 정보을 배열에 저장하여 기억하는 원리가 포함된 코드입니다.
let stateStore = []; // 🔥 컴포넌트 바깥에 있어서 렌더링과 관계없이 유지됨
let stateIndex = 0; // 🔥 컴포넌트 바깥에 있지만, 함수 내부에서 매번 0으로 초기화되도록 설계됨
function useState(initialState) {
if (stateStore[stateIndex] === undefined) {
stateStore[stateIndex] = typeof initialState === "function"
? initialState() // 🟢 함수라면 한 번만 실행
: initialState;
}
let currentIndex = stateIndex;
function setState(newState) {
stateStore[currentIndex] = newState;
render(); // 리렌더링 트리거
}
return [stateStore[stateIndex++], setState];
}
stateIndex = 0;
useState(0); // stateStore[0] = 0
useState("hello"); // stateStore[1] = "hello"
stateIndex === 2 (컴포넌트 실행 끝)
function App() {
const [id, setId] = useState(veryHeavyComputation); // ✅ 지연 초기화 적용
return <div>{id}</div>;
}
위처럼 useState에 함수를 직접 전달하면 useState 내부에서 함수인지 확인 후 한 번만 실행되므로 불필요한 연산을 방지할 수 있습니다.
const [id, setId] = useState(() => veryHeavyComputation());
이렇게 작성해도 동일하게 동작합니다. 함수가 실행되지 않고 useState 내부에서만 실행되도록 하는 역할을 합니다.
| 초기값 설정 방식 | 내부 동작 | 성능 문제 |
|---|---|---|
useState(veryHeavyComputation()) | veryHeavyComputation()이 즉시 실행됨 | ❌ 렌더링마다 실행됨 (비효율적) |
useState(veryHeavyComputation) | 함수인지 체크 후, 한 번만 실행 | ✅ 최적화됨 |
useState(() => veryHeavyComputation()) | 동일하게 한 번만 실행 | ✅ 최적화됨 |
useState(함수) 형태로 사용해야 합니다!