const [items, setItems] = useState(0);
위와 같은 useState 훅의 사용법은 아주 흔한 방식이다. 하지만 initialState가 다음과 같다면 어떨까?
const App = () => {
const init = () => {
console.log("This is my init Func");
const items = localStorage.getItem("myItems");
return items.split(",").map((data) => data * 2);
};
const [content, setContent] = useState(init());
const inputRef = useRef();
return (
<div>
<ul>
{content.map((d, index) => (
<li key={index}>{d}</li>
))}
</ul>
<input type='text' ref={inputRef} />
<button
onClick={() => {
setContent((prev) => [...prev, Number(inputRef.current.value)]);
}}
>
Submit
</button>
</div>
);
};
localStorage에 접근하는 등 복잡한 연산을 initialState에 사용해야 할 때가 있다. 하지만 다음과 같은 사용은 문제를 발생시킨다.
보이는 것과 같이 리렌더링이 발생하면 initialState를 구성하는 함수가 다시 호출된다.
비용이 큰 로직이라면 이것은 굉장히 비효율적이다.
다행히 리액트는 이런 상황을 고려했는지 해결책을 마련해놨다.
const [content, setContent] = useState(init);
간단하게 함수 호출이 아닌 함수 그 자체를 전달해주면 된다.
const [content, setContent] = useState(() => init());
혹은 로직에 따라 이렇게 화살표 함수를 이용할 수도 있다.
const [text, setText] = useState(() => 'hello');
그렇다면 모든 initialState를 함수로 전달하면 되지 않을까? 최초 렌더링에만 initialState를 쓰는 게 당연하니 말이다.
함수 생성도 비용임을 인지해야한다. 불필요한 함수 생성이 반복되다 보면 프로그램의 성능에 악영향을 끼칠 수 있다.
If the initial state is the result of an expensive computation, you may provide a function instead, which will be executed only on the initial render.
공식 문서에 써있듯 고비용의 연산이 포함된 경우에만 사용하도록 하자.