React의 핵심, 상태관리와 데이터 흐름

thisishwarang·2024년 4월 26일
0
post-thumbnail

❓상태관리란?

react를 가장 처음 배우기 시작하면 jsx문법 다음으로 보통 상태관리를 도와주는 useState라는 훅을 배우게 됩니다. jsx문법은 html과 비슷한 구조를 가지고 있어 비교적 이해가 쉽지만 useState는 처음보는 개념이라 이해가 잘 안될 수 있습니다.

useState가 상태관리를 하는 훅이라는건 배워서 알겠는데 그럼 상태관리가 무엇이고 왜 필요한걸까요?

React에서 상태관리란 애플리케이션의 데이터를 추적하고 조작하는 것을 의미합니다. React에서 상태는 컴포넌트 내부에서 관리되며, 컴포넌트의 상태가 변경될 때마다 React는 해당 컴포넌트를 다시 렌더링하여 사용자 인터페이스를 업데이트합니다.

예를들어 어떤 웹사이트의 마이페이지에서 내 정보를 변경하고자 하는 상황이라고 생각해봅시다.
어떤 식의 흐름으로 내 정보를 변경할 수 있을까요?

간단하게 생각해보면

  1. 변경하기 전 내 정보가 보임 (ex. 이름 : thisishwarang)
  2. 이름을 입력하는 칸에서 이름을 수정함 (ex. 이름 : coolguy)
  3. 수정완료 버튼을 클릭하여 이름이 변경되는것을 확인할 수 있음. (화면상에서 이름이 변경됨)

이런 데이터 변경과 적용을 javascript로 구현하려면 이름을 변경하는 함수를 호출하거나 강제로 페이지를 reload하는 방식으로 이름을 변경할 수 있겠죠?

하지만 react에서는 이러한 데이터를 추적하고, 조작하는 상태관리를 통해 빠르고 쉽게 대응할 수 있습니다.

그럼 이제 상태관리라는 개념은 알았고, 위에서
React에서 상태는 컴포넌트 내부에서 관리되며, 컴포넌트의 상태가 변경될 때마다 해당 컴포넌트를 다시 렌더링하여 사용자 인터페이스를 업데이트한다. 라고 언급했습니다.

제 프로젝트 폴더구조의 일부분인데
React로 개발을 하면 정말 많은 컴포넌트가 나올 수 있습니다.
어떤 기준으로 컴포넌트를 분리하는가에 따라 달라질 수 있겠지만 하나의 컴포넌트가 너무 크고 복잡한 기능을 가지지 않으면서
재사용을 하기 위해서 컴포넌트를 설계하다 보면 많은 컴포넌트가 생길것이고
이 컴포넌트간 데이터를 전달해야할 일이 많을겁니다.

각 컴포넌트에는 컴포넌트 내부에서 관리하고 있는 상태들이 있을것이고,
이 상태를 다른 컴포넌트에서 필요로 하여 상태를 props를 통해 주고 받을텐데, 이러한 상태(state)들이 너무 복잡하게 얽혀있다면 컴포넌트간 상호 의존성이 높아져서 원하지 않는 에러나 컴포넌트의 불필요한 재렌더링이 일어날 수 있습니다.

👿 상태관리를 잘못했을때 벌어지는 문제

useState를 배우고 이제 개발을 하다보면 이러한 문제가 발생할 수 있습니다.
현재 컴포넌트에서 2~3단계 이상의 상위 컴포넌트에 있는 데이터를 필요로 할때 한 단계씩 props로 전달해주고, props로 전달해주고, props로 전달해주고......

function App() {
  return <GrandParent value="Hello World!" />;
}

function GrandParent({ value }) {
  return <Parent value={value} />;
}

function Parent({ value }) {
  return <Child value={value} />;
}

function Child({ value }) {
  return <GrandChild value={value} />;
}

function GrandChild({ value }) {
  return <Message value={value} />;
}

function Message({ value }) {
  return <div>Received: {value}</div>;
}

이러한 반복되는 props의 전달을 props drilling 이라고 합니다.

⚙️ props drilling

props drilling을 막으려면 어떻게 해야 할까요??

1. Context API

Context API는 React에서 상태를 전역적으로 관리하고 컴포넌트 간에 데이터를 전달하는 데 사용되는 기능입니다. 이를 통해 중첩된 컴포넌트 구조에서 데이터를 props로 계속해서 전달할 필요 없이 데이터를 한 번에 상위 컴포넌트에서 하위 컴포넌트로 전달할 수 있습니다.

꼭 전역적으로 상태를 관리한다기 보단 props가 아닌 다른 방식으로 컴포넌트간 데이터를 주고받는 방식입니다.
주고받을 데이터를 필요로하는 가장 최상단 컴포넌트를 Context API를 통해 만든 Provider로 감싸주면 그 내부의 모든 컴포넌트에서 전역적으로 데이터를 사용할 수 있는 개념입니다. 이러한 방식으로 props를 연속적으로 전달하는 방식을 막을 수 있습니다.

  • 위 props drilling 예시 코드에서 Context API를 사용한다면 GrandParent를 Context API로 만든 Provider로 감싸주면 그 아래로 모든 컴포넌트에서 어디서든 useContext훅으로 바로 데이터를 사용할 수 있습니다.

2. 상태관리 라이브러리

Context API는 React에서 제공하는 공식 상태관리 도구인 반면 외부 라이브러리를 활용하여 상태관리를 할 수 있습니다. 예를들어 Redux, MobX, Recoil, Zustand 등등이 있는데 대부분 비슷한 개념으로 상태를 추적하고, 변경사항이 생겼을때 즉시 반영하여 전역적으로 상태관리를 도와주는 라이브러리들입니다.

3. Custom Hook

Context API나 상태관리 라이브러리로 단순히 데이터를 담고있는 변수나 함수를 주고 받았다면, Custom Hook은 하나의 로직을 전달하는 개념입니다.

Custom Hook은 React 함수 컴포넌트에서 상태 로직을 재사용하기 위한 방법 중 하나입니다. 커스텀 훅을 사용해서 상태 관리, side effect 처리, 다른 훅을 조합하는 등의 로직을 하나의 독립적인 함수로 만들어서 여러 컴포넌트에서 재사용할 수 있습니다.


렌더링을 효과적으로 관리하려면?

상태관리를 중요하게 다뤄야하는 이유 중 props drilling 뿐만 아니라 또 다른 이유는 바로 상태가 변경되면 React는 리렌더링을 자동으로 진행하기 때문입니다.

<컴포넌트의 리렌더링 조건>
1. 부모에서 전달받은 props가 변경될때
2. 부모 컴포넌트가 리렌더링 될 때
3. 자신의 state가 변경 될 때

props를 주고받고, 상태관리 라이브러리를 사용해서 데이터들을 전역적으로 다루다보면 불필요한 컴포넌트의 리렌더링이 발생하는데 이를 막을 방법에는 무엇이 있을까요?

1. useMemo

useMemo는 React 훅 중 하나로, 메모이제이션된 값을 반환합니다. 주어진 입력값이 변경되지 않는 한 이전에 계산된 값을 재사용합니다.

예를 들어, 계산 비용이 높은 작업이나 복잡한 계산을 수행하는 경우, 해당 계산 결과를 useMemo를 사용하여 메모이제이션하여 다시 계산하지 않고 이전 결과를 재사용할 수 있습니다. 이렇게 함으로써 같은 입력값에 대한 반복적인 계산을 방지하여 렌더링 성능을 향상시킬 수 있습니다.

2. useEffect

useEffect의 의존성배열에 따라 불필요한 리렌더링이 발생하는 경우가 있습니다. 이를 방지하기 위해 의존성배열을 최적화하고 필요시에만 렌더링 되도록 구현해야합니다.


불필요한 리렌더링을 막기 위해서 저는 렌더링이 일어나는 state들을 콘솔에 찍어보며 방지하고, useEffect에 사용되는 의존성배열을 최대한 최적화 시켜 state간의 꼬임을 방지하고자 노력합니다.
다음에는 useMemo를 직접 사용해보고, 또 다른 렌더링을 효과적으로 관리하는 방법은 없는지 찾아봐야겠습니다.

0개의 댓글