리액트와 상태관리
React에서 상태관리는 왜 필요한가?
상태란
상태란 컴포넌트 안에서 관리되는 변수를 말한다.
컴포넌트들은 서로 상태를 공유할 수 있는데, 하지만 자식간의 공유는 불가능 하기 때문에 부모를 거쳐서 상태를 받아와야 한다.
상태관리가 왜 필요한가?
실제로 한 프로젝트에서 Header에 있는 Reset 버튼에 대한 상태를 Main에 있는 CardList에 전달하기 위해 Header와 Main의 공통된 부모인 App에 Header의 상태를 전달한 뒤, App에서 Main으로 전달했다. App은 해당 State를 사용하지 않지만, 그저 Main에 전달해준다는 목적으로 상태를 넘겨받은 것이다.
이렇듯 state가 전달될 때 중간 컴포넌트는 사용하지 않는 props를 넘겨받아야한다는 문제점이 발생한다.
컴포넌트 트리가 커지고 state를 전달하는 데 거치는 중간 컴포넌트들이 많아질 수록 state 관리가 어려워지는데, 이러한 문제를 props drilling이라고도 한다. 만약 이러한 props 공유가 더 깊고 복잡한 컴포넌트 구조에서 이루어진다면,좀 더 효과적으로 상태를 관리하는 게 필요할 것이다.
관리 해야 하는 상태에 대한 기준은 무엇인가?
리액트는 받은 데이터를 UI에서 보여주기만 하는 단방향 흐름(Flux)를 가진다.
공식문서에서 제시하는 지역 상태와 전역 상태의 기준은 다음과 같다.
- 기본적으로 일반적인 경우에는 지역 상태로 데이터를 관리하는 것을 권장한다.
- 지역 상태로 데이터를 관리 시 다수의 컴포넌트 간에 상태 의존성이 높아진다면 전역 상태로 데이터를 관리하는 것을 권장한다.
- 전역 상태 관리 시 서버에서 가져오는 데이터(db)와, 단순하게 UI 상태를 나타내는 데이터는 분리하여 다룬다.
- 서버 데이터 캐싱 시 전역 상태로 다루면 안된다. 서버 상태를 관리하려면, SWR이나 React-Query 와 같은 서버 캐싱 전용 라이브러리를 사용하는 것을 권장한다.
어떤 상태관리 라이브러리를 어떤 상황에서 사용해야 할까?
React Context API
- react 자체적으로 제공하는 전역상태관리 API
- 최상단에 Provider을 배치함으로써 context를 구독하는 컴포넌트에게 변화를 알리는 역할을 한다. 해당 변화를 인지한 컴포넌트는 Provider가 전달한 prop이 바뀔 떄마다 리랜더링을 한다.
- 따라서 컴포넌트가 리랜더링됨에 따라 비효율적일 수 있다는 단점을 가지며, 다크모드나 언어설정과 같은 자주 변하지 않는 변수를 다루는 데 쓰는 게 좋다.
Redux
- js 앱을 위한 예측 가능한 상태 컨테이너
- flux 패턴을 사용하기 때문에 action → dispatch → reducer → view의 흐름을 가지며, 단방향 데이터 흐름으로 구성된다.
- Flux 흐름
- Action은 버튼을 누르는 것과 같은 이벤트의 이름이다.
- Dispatcher는 Action에서 발생한 이벤트의 이름에 따라 처리할 행동을 알려준다.
- Store는 데이터가 저장되어 있는 저장소이다. Dispatcher에서 받은 행동에 따라 데이터를 핸들링한다.
- View에서는 Store에서 데이터를 받는다. 그리고 Action을 통해 이벤트를 발생은 시키지만, 데이터는 전달하지 않는다.
Recoil
- React 앱을 위한 상태관리 라이브러리
- atom이라는 상태 개념을 사용하는데, 컴포넌트들이 상태를 구독하는 단위라고 생각하면 된다. atom의 변화가 있을 때마다 해당 구독하는 컴포넌트가 리랜더링된다.
- atom → selector → view 데이터 흐름을 가진다. 또한 다른 라이브러리와 달리 비동기 기반이기 떄문에 동시성 모드를 지원할 수 있다.
렌더링을 효과적으로 하는 법
React에서 렌더링을 효과적으로 관리하는 방법은 무엇이 있을까?
리액트의 렌더링에 대해
렌더링은 코드의 내용을 실제 화면에 그리는 작업을 의미한다. 리액트는 props와 state를 기반으로 UI를 결정한다.
Jsx 구문으로 작선된 js 파일을 createElement을 통해 UI로 해석한 뒤, DOM단에서의 변경 사항을 수집한다. 그 후, 컴포넌트를 렌더링하고 변경 사항을 계산하는 렌더 단계를 거쳐 추후에 계산된 변경사항까지 DOM에 적용하는 커밋단계를 거친다. 이때 훅의 개념이 개입하는데, 리액트 훅의 순서도는 아래와 같다.
부모 컴포넌트가 랜더링 될 때 props의 변경여부와 관계없이 하위 컴포넌트가 모두 렌더링된다. 업데이트할 떄마다 전체 앱을 다시 그리는 것처럼 동작한다. 또한 리액트에서 렌더링은 순수한 요구동작을 가져야하며, 어떠한 사이드 이펙트가 없어야한다는 규칙을 가진다.
리액트의 렌더링 최적화
렌더링 작업의 효율적인 실행을 위해, DOM의 변경사항을 렌더링 시에 잘 참고하는 게 중요하다. 변경되지 않았는데 UI를 다시 그리는 불필요한 렌더링은 자원이 낭비될 수 있기 때문이다. 이를 위해 React는 Virtual DOM을 사용하여 실제 DOM 조작을 최소화하고, 변경된 부분만 업데이트하도록 최적화되어 있다.
우리는 클래스 컴포넌트에서 PureComponent를 사용해 라이프 사이클 메서드 없이도 렌더링을 효율적으로 관리하는 방법을 사용할 수 있다. 또한, 추가적으로 shouldComponentUpdate() 메서드를 구현하여 props와 state의 변화를 감지하고 불필요한 렌더링을 방지할 수 있다.
그 외에도 함수형 컴포넌트에서 React.memo()를 사용하여 이전에 렌더링된 결과를 캐싱하고, 동일한 props가 전달되었을 때 불필요한 렌더링을 방지하거나, 리스트를 렌더링할 때 key prop를 사용하여 React가 변경된 요소만 업데이트할 수 있도록 최적화할 수 있다. 또한, React.lazy()와 Suspense를 사용하여 코드 분할을 활용해, 필요한 시점에 로드할 수 있도록 렌더링을 최적화할 수 있다.
이를 위해 어떤 식으로 비즈니스 설계를 진행해야 할까
렌더링을 효과적으로 관리하기 위해서는 컴포넌트 구조와 상태 관리 등의 구현 방식을 효율적으로 사용해야한다.
컴포넌트를 기능 단위로 분리하여, 불필요한 렌더링을 방지해야한다. 또한 상태의 변경을 효과적으로 관리하고, 불필요한 렌더링을 방지하기 위해 적절한 상태 관리 라이브러리 사용이 필요하다.
비동기 처리를 위해서는 Promise, async/await 등의 기능을 활용할 수 있는데, 비동기 처리를 통해 데이터를 효과적으로 로드하고, 불필요한 렌더링을 방지할 수 있다. 또한 최적화 기능 활용하여 React가 제공하는 React.memo(), shouldComponentUpdate(), PureComponent를 통해 불필요한 렌더링을 방지할 수 있다.
또한 React Developer Tools를 사용하여 컴포넌트의 렌더링 성능을 모니터링하고, 불필요한 렌더링을 식별하며 렌더링 성능에 대한 관리를 진행해야한다.
자료출처
- https://velog.io/@wjdwl002/React-%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B9%84%EA%B5%90%EB%B6%84%EC%84%9D-Redux-Recoil-%ED%8E%B8
- https://han-py.tistory.com/487
- https://velog.io/@superlipbalm/blogged-answers-a-mostly-complete-guide-to-react-rendering-behavior
- https://www.nextree.io/riaegteu-rendeoring-mic-coejeoghwa/
- 내 친구 GPT
'Web' 카테고리의 다른 글
sopt 웹잼 | 끝내며 적는 고민의 흔적들 (0) | 2023.07.21 |
---|---|
sopt 웹잼 | 데모데이 d-7을 남겨놓고 적는 회고록 (0) | 2023.07.16 |
[React] 프로젝트 구조를 설계할 때 무엇을 고려해야하는가? (0) | 2023.04.23 |
프론트엔드 개발자가 바라볼 수 있는 웹 최적화 (0) | 2023.04.09 |
[공유] 주니어 프론트엔드 엔지니어에서 벗어나는 방법 (0) | 2022.12.29 |