리액트의 상태 관리를 위한 훅인 useState와 useReducer는 첫 번째 렌더링에서만 평가되는 초기화 함수를 받을 수 있다. 예를 들어서 비용이 아주 큰 연산을 하는 함수를 이용해서 초기화 값을 만들어야 한다고 하자. 간단하게 이렇게 해볼 수 있다.
하지만 이렇게 하면 veryExpensiveInitFunction()의 결과는 초기 렌더링에만 사용됨에도 불구하고 매 렌더링마다 호출된다.
이를 개선하기 위해 useState와 useReducer에 초기화 함수를 전달할 수 있다. 이 함수는 훅이 호출되기 전까지 호출되지 않으며 컴포넌트가 마운트될 때 한 번만 호출된다. useReducer같은 경우 2번째 인자 initialArg를 3번째 인자인 init 함수를 통해 초기화한 값을 state 초기값으로 사용한다.
useState와 useReducer는 packages/react/src/ReactHooks.js에 있다.
그럼 핵심인 resolveDispatcher는 어디에 있을까? packages/react/src/ReactHooks.js에 같이 있다.
src/ReactCurrentDispatcher.js에 ReactCurrentDispatcher가 있다.
찾아보니 다른 곳에서 ReactCurrentDispatcher.current를 주입해 준다고 한다. 이는 ReactFiberHooks.js에서 이루어진다.
대략 현재 상태도 메모이제이션된 상태도 없으면 HooksDispatcherOnMount를 사용하고, 그렇지 않으면 HooksDispatcherOnUpdate를 사용한다는 걸 알 수 있다.
우리는 '초기화 함수'에 대한 동작이 궁금하니까 HooksDispatcherOnMount를 살펴보자.
mountState는 mountStateImpl을 호출하는데 거기 보면 이렇게 되어 있다. typeof로 initialState가 함수인지 판단하고 함수면 호출해서 초기값을 만들어낸다.
이후 state update 함수를 보면 거기에도 사용되지는 않지만 initialArg가 인수로 들어가게 된다. 이때 만약 이렇게 초기화 함수로 전달된 인수가 있다면 update할 때는 초기화 함수를 마운트 시에 딱 1번 호출해서 만들어낸 값이 들어가지만 그렇지 않으면 initialArg가 들어가게 되는데 이때 만약 initialArg가 함수 전달이 아닌 함수 호출(veryExpensiveInitFunction())이라면 이 큰 비용의 함수가 매 렌더링마다 호출되게 된다.
mountReducer는 이렇게. init 인수가 있는지 없는지에 따라서 초기값을 만들어낸다.