게으른 초기화 (lazy initialization) 이란?
React에서 게으른 초기화는 상태(state) 초기화 시점에서 초기화 작업이 비용이 많이 들거나 시간이 오래 걸릴 때 유용하게 사용할 수 있는 기술이다.
예를 들어 다음과 같은 코드를 보자.
const veryHeavyWork = () => {
let expensiveValue = 1;
console.log('very heavy work called');
// 연산 비용이 아주 비싼 함수
// ......
return expensiveValue;
};
export const LazyInitComponent = () => {
const [expensiveState, setexpensiveState] = useState(veryHeavyWork());
const [count, setCount] = useState(0);
useEffect(() => {
console.log('rerender');
}, [count]);
return (
<div>
<button onClick={() => setCount(count + 1)}>click me</button>
</div>
);
};
위의 코드에서 컴포넌트 안의 state(count
)를 변화 시킬 때 마다 리렌더링이 일어난다. 리렌더링이 일어날 때, expensiveState
에 초깃값으로 들어가기 위해 사용된 veryHeavyWork
함수가 계속해서 호출된다.
이런 경우 게으른 초기화를 사용하면, 리렌더링이 되더라도 초깃값으로 설정 되었다면, 값이 비싼 함수를 다시 실행시키지 않을 수 있다.
const [expensiveState, setexpensiveState] = useState(() => veryHeavyWork());
방법은 위와 같이 간단하다. 함수를 실행한 결과값을 전달하는 것이 아닌, 함수를 반환하는 함수를 전달하면 된다.
무슨 차이점이지?
자바스크립트의 실행 순서와 리액트가 초깃값이 있는지를 판단하는 순서에 따라 다르다.
const [expensiveState, setexpensiveState] = useState(veryHeavyWork());
위와 같은 경우는 useState
에 초깃값이 있는지 없는지를 판단하기 전에 veryHeavyWork
라는 함수를 실행시킨다. 함수를 실행 후, 반환 값을 useState
에 넣으려 했지만, 이미 초깃값이 있기 때문에 무시된다.
const [expensiveState, setexpensiveState] = useState(() => veryHeavyWork());
그러나 위와 같이 작성한다면, veryHeavyWork
함수를 실행하기 전에 useState
에 함수를 반환하는 함수 그 자체가 전달된다. 따라서 함수라는 초깃값이 이미 할당되어있기에 안의 내용은 무시되고 veryHeavyWork
함수는 실행되지 않는다.
언제 쓰면 좋을까?
위에서 계속 설명한 것과 같이 초깃값을 생성하는데 연산이 오래 걸릴 경우에 쓰면 좋다. 예를 들어, localStorage
나 sessionStorage
에 접근할 때, 배열에 관한 메서드를 사용할 때 등이다.
useEffect를 이용한 초기화와의 차이
useState
의 게으른 초기화와 useEffect
를 통해 컴포넌트가 렌더링 될 때 state를 초기화 하는 것 사이에는 몇 가지 차이가 있다.
초기화 시점
게으른 초기화: state 변수 선언 시점에 직접 값을 설정한다. 컴포넌트가 처음 렌더링되기 전에 값이 설정되므로, 컴포넌트가 마운트되고 첫 렌더링이 일어나기 전에 값을 사용할 수 있다.
useEffect: 컴포넌트가 렌더링 된 이후 초기화
용도
게으른 초기화: 컴포넌트가 마운트되기 전에 값을 사용해야 하는 경우
useEffect: 비동기 작업이나 side effect가 발생할 경우