리액트 상태관리
프론트엔드 공부 중 상태관리의 중요성에 대해 자주 듣고 했었는데,
항상 '좋은' 상태관리는 어떻게 하는 것일까? 라는 궁금증이 있었습니다
저와 같은 궁금점을 가지고 있었던 분들에게 도움이 되는 글이 되기를 바라며 시작해 보겠습니다!
상태(state) 관리란?
State는 이벤트에 의해 변경되는 동적인 데이터로, 시간의 흐름에 따라 변화하는 데이터이자 렌더링에 영향을 끼치는 정보를 가진 객체라고 합니다.
웹페이지에서 데이터는 사용자의 이벤트에 따라 변경되기도 하고 이 변경된 데이터가 UI에 다시 출력되기도 합니다. 즉, State(상태)는 웹 애플리케이션에서의 현재 상태나 정보를 반영하는 값으로, 페이지의 렌더에 영향을 미칩니다. 상태 객체는 리액트의 각 컴포넌트에 담겨 있으며 상태가 변경될 시 컴포넌트는 리렌더링 되어 해당 값을 반영합니다.
이때 상태들은 다양한 방식으로 존재하여 단일 컴토넌트 내에서 관리되거나 여러 컴포넌트 간에 사용되기도 하며, 전역적으로 사용되기도 합니다.
전역상태 (Global State)
1) 프로젝트 전역에 영향을 미쳐 프로그램 어디서나 접근할 수 있는 상태
2) Props Drilling 방식으로 부모 컴포넌트가 자식 컴포넌트에게 데이터를 전달함
컴포넌트 간 상태 (Cross Component State)
1) 여러 컴포넌트에서 관리되는 상태
2) Props Drilling이 일부 필요함
지역상태 (Local State)
1) 특정 단일 컴포넌트 내에서만 관리되는 상태
2) 다른 컴포넌트와 데이터를 공유하지 않음
그럼... 상태 관리는 왜 해야 되는데?!
프로젝트 규모가 커지고 복잡해질수록 더욱 많은 양의 데이터를 관리해야 합니다.
이와 같은 상황에서 단일 컴포넌트에서 벗어나 여러 컴포넌트 간 데이터를 공유할 경우를 값 전달이 복잡해지고 비효율적으로 작동합니다. 각 컴포넌트 간의 직접적인 데이터 전달이 어려워 데이터를 부모 컴포넌트에 보내고 필요한 상태 데이터를 다시 받아와야 하는 데 이 과정에서 props drilling과 같은 문제가 나타날 수도 있습니다.
Props Drilling이란?
Prop drilling은 React 애플리케이션에서 데이터를 전달하기 위해 필요한 과정을 말하는 것으로, 컴포넌트 트리에서 데이터를 하위 컴포넌트로 전달할 때 중간 컴포넌트를 매개하는 것을 의미합니다.
위의 그림을 살펴 보면, ChildC가 Parent 컴포넌트로부터 프로퍼티를 전달 받기 위해 해당 프로퍼티를 필요로 하지 않은 ChildA, ChildB 컴포넌트에서도 해당 값을 받고 전달해야 합니다...
function Toggle() {
const [isChecked, setIsChecked] = React.useState(false);
const toggle = () => setIsChecked(prevState => !prevState);
return <Checkbox isChecked={isChecked} onToggle={toggle} />;
}
function Checkbox({ isChecked, onToggle }) {
return (
<div>
<CheckboxMessage isChecked={isChecked} />
<CheckboxButton onToggle={onToggle} isChecked={isChecked} />
</div>
);
}
function CheckboxMessage({ isChecked }) {
return <div>{isChecked ? "The checkbox is checked" : "The checkbox is not checked"}</div>;
}
function CheckboxButton({ onToggle, isChecked }) {
return (
<input
type="checkbox"
checked={isChecked}
onChange={onToggle}
/>
);
}
Checkbox 컴포넌트는 isChecked와 onToggle를 필요로 하지 않지만, 컴포넌트 내에 자식 컴포넌트로 CheckboxMessage, CheckboxButton를 담고 있기 때문에 해당 props를 전달 받고 있습니다.
재사용성을 위해 컴포넌트를 분리했다 하더라도 오히려 props를 매개하기 위한 컴포넌트의 발생으로 상하위 컴포넌트의 결속력이 강해지는 문제가 발생한 것입니다.
이렇게 props drilling이 많아질 경우 어떤 문제점이 생길까요?
맞습니다... 제가 전달 받고자 하는 프로퍼티의 출처가 어떤 컴포넌트인지 찾기 어려운 문제가 발생합니다.
Props Drilling은 명시적인 값을 사용하여 프로젝트 규모가 작을 때는 값을 추적하는 게 용이하지만 프로젝트 규모가 커질수록 중간 컴포넌트에 불필요한 프로퍼티 전달이 많아져 누락된 프로퍼티를 인지하거나 프로퍼티의 변경을 추적하는 데 어렵고, 웹 성능 저하 문제를 겪게 됩니다.
Prop Drilling... 어떻게 개선할 수 있을까요?
Redux 또는 다른 상태 관리 라이브러리
Redux는 대표적인 상태 관리 라이브러리로 '예측 가능한 상태 컨테이너'입니다.
Redux는 특정 데이터가 어디에서 언제 변경되었는지를 알려주기 위해 만들어진 라이브러리로 Store에 상태를 저장해 앱 내 어디에서든지 접근 가능하도록 '중앙 상태 관리 방식'으로 동작합니다. 단 하나만 존재하는 Store에 전역 상태를 저장하고 필요한 컴포넌트에서 상태를 가져와 사용해 어디서든 전역적으로 접근 가능하다는 장점이 있습니다.
하지만 리덕스는 안정적인 상태 유지를 위해서 강한 제약을 요구하는데, 어플리케이션 상태 / 무엇이 일어나는지 / 어떻게 바꾸는 지 구분해서 개발해야 합니다. 이러한 제약은 어플리케이션이 비대화 될수록 이런 상태 관리 사이클 관리를 위한 코드가 복잡해져 확장성이 떨어진다는 단점이 있습니다.
Custom Hooks
Custom Hooks은 컴포넌트 내 로직을 분리하는 것으로, props에 관련된 로직만 재사용 가능한 함수로 만들 수 있습니다. 즉, 커스텀 훅에서는 상태와 관련된 로직을 처리하고, 필요한 컴포넌트에서 훅을 호출하여 props의 값을 가져올 수 있습니다. 이러한 방법은 보다 간편하게 데이터 전달을 가능케 하며 props drilling의 문제를 해소할 수 있습니다.
Context API
React의 Context를 활용한다면 데이터를 전역적으로 공유해 전역 상태의 props를 원하는 하위 컴포넌트에 주입할 수 있습니다. Context 컴포넌트에 상태값을 선언한 다음 필요한 컴포넌트에서 useContext 훅을 사용한다면 컨텍스트에서 내려주는 데이터에 직접 접근할 수 있습니다. 즉, 중간 컴포넌트를 거치지 않고도 데이터를 전달할 수 있습니다.
하지만, Context API는 상태를 주입해 주는 역할에 불과하며 불필요한 렌더링을 막아주는 기능은 없다는 단점을 지닙니다.
렌더링은 어떻게 효과적으로 관리할 수 있을까요?
상태 관리는 렌더링에 직접적으로 영향을 미칩니다. 즉 상태를 잘 관리해야 불필요한 렌더링이 방지될 수 있겠죠?
state의 배치
우선, state를 관련 컴포넌트들과 최대한 가까이 배치하는 것이 첫 번째 방법이 될 수 있습니다. state가 관련 컴포넌트와 멀어질수록 상태-컴포넌트 사이의 관계없는 컴포넌트의 rerender 발생 위험이 커집니다. 또 state가 관심사에 따라 잘 분리가 되어야 코드 수정시 사이드 이펙트를 최소화할 수 있습니다.
React Developer Tools
효율적인 렌더링을 하게끔 도와주는 대표적인 도구입니다. 해당 도구를 통해 렌더링의 발생 과정을 파악할 수 있고, 단순 구조뿐만 아니라 props와 내부 hooks 등 다양한 정보를 확인할 수 있게 도움으로써 더 효율적인 렌더링을 관리할 수 있습니다.
React의 자체 API, Hooks
memo와 useMemo, useCallback 훅 등을 사용할 수 있습니다. 단, 메모이제이션은 필요한 곳에만 신중히 해야 합니다. 이는 결과를 비교하고 렌더링. 재계산이 필요한지 확인하는 작업 혹은 이전의 결과물을 다시 꺼내와야 한다는 점에서 또 다시 렌더링 과정이 발생할 수 있기 때문에 신중히 사용해야 합니다.
글을 마치며...
이번에 상태 관리에 대해 조사하며 리액트 상태 관리를 이렇게나 효율적으로 할 수 있고, 이를 위한 다양한 방법이 있음을 알게 되었습니다. 사실, 상태 관리에 대해 깊게 공부해 보거나 이를 크게 유념하며 프로젝트에 적용해 본 경험이 없기에 더욱 새롭게 배워나갈 수 있는 시간이었던 것 같습니다! 리액트 개발 시 항상 state 상태를 많이 사용하게 되는데, 이제는 좋은 상태 관리를 할 수 있게+불필요한 렌더링을 지양하며 보다 나쁜 상태관리를 지양할 수 있게 노력해 보아야겠다 생각했습니다! ㅎㅎㅎ...
부족한 글이지만 여러분들께 도움이 되었길 바라며, 궁금한 부분이 있다면 언제든 댓글 달아 주세욧!! 읽어주셔서 감사합니다. :)
