
리액트는 함수 컴포넌트에서도 상태(state)를 사용하거나 클래스 컴포넌트의 생명주기 메서드를 대체할 수 있도록 훅(Hook)을 도입했습니다. 훅은 리액트의 중요한 기능들을 함수 컴포넌트에서도 사용할 수 있게 하며, 클래스 컴포넌트보다 더 간결하게 작성할 수 있는 장점이 있습니다. 그 중에서도 useState는 가장 많이 사용되는 훅 중 하나입니다.
useState는 함수 컴포넌트 내부에서 상태를 정의하고 관리할 수 있게 해주는 훅입니다. useState는 초깃값을 인수로 받아들이며, 반환 값은 배열입니다. 이 배열의 첫 번째 원소는 상태 값 자체이고, 두 번째 원소는 상태 값을 변경할 수 있는 함수(setState)입니다.
예를 들어:
const [count, setCount] = useState(0);
위 코드에서 count는 상태 값이고, setCount는 이 상태 값을 변경할 수 있는 함수입니다. 초깃값으로 0을 설정했습니다.
리액트에서 렌더링은 함수 컴포넌트의 return 결과와 클래스 컴포넌트의 render 함수의 실행 결과를 이전 리액트 트리와 비교하여 리렌더링이 필요한 부분만 업데이트하는 방식으로 이루어집니다.
리액트는 함수 컴포넌트에서 반환된 return 값을 비교하여 렌더링을 수행합니다. 함수 컴포넌트는 매번 렌더링이 발생할 때마다 함수가 새롭게 실행되고, 이 실행된 함수에서 상태는 매번 초기화됩니다. 그러나 useState 훅을 사용하면 함수가 실행될 때마다 상태 값을 유지할 수 있습니다.
리액트는 클로저를 이용하여 상태 값을 유지합니다. 클로저란, 어떤 함수(useState) 내부에 선언된 함수(setState)가 함수의 실행이 종료된 이후에도 (useState가 호출된 이후에도) 지역 변수인 state를 계속 참조할 수 있는 것을 의미합니다. 이로 인해 함수 컴포넌트가 매번 실행되더라도 useState에서 이전 값을 정확하게 가져올 수 있게 됩니다.
useState에 변수 대신 함수를 인수로 넘기는 것을 게으른 초기화(lazy initialization)라고 합니다. 이 게으른 초기화 함수는 상태가 처음 만들어질 때만 사용됩니다. 이후에 리렌더링이 발생되면 이 함수의 실행은 무시됩니다.
const [count, setCount] = useState(() => {
console.log("Initial state");
return 0;
});
위 코드에서 함수가 인수로 넘겨졌으므로, 최초 렌더링 시에만 console.log("Initial state")가 실행됩니다. 이는 복잡한 초기화 로직을 포함한 경우 성능 최적화에 유리합니다.
리액트에서는 무거운 연산이 필요할 때 게으른 초기화를 사용하는 것이 좋습니다. 예를 들어, localStorage나 sessionStorage에 접근하거나, 배열의 map, filter, find와 같은 메서드를 사용할 때, 혹은 초기 값 계산을 위해 함수 호출이 필요한 경우 등에 게으른 초기화를 사용하면 성능을 개선할 수 있습니다.
const [data, setData] = useState(() => {
const savedData = localStorage.getItem("myData");
return savedData ? JSON.parse(savedData) : [];
});
위 예제에서 localStorage에서 데이터를 읽어와 초기 상태로 설정하는 데 게으른 초기화를 사용했습니다. 이렇게 하면 최초 렌더링 시에만 localStorage에 접근하고, 이후 리렌더링 시에는 불필요한 연산을 피할 수 있습니다.
useState는 함수 컴포넌트에서 상태를 관리하는 가장 기본적인 훅입니다. 클로저를 이용해 상태 값을 유지하고, 게으른 초기화를 통해 성능을 최적화할 수 있습니다. 리액트의 함수 컴포넌트는 이러한 훅을 통해 더욱 간결하고 효율적인 코드 작성을 가능하게 합니다.