이 포스트는 아래 출처의 블로그 글을 번역한 내용입니다.
React는 2013년 5월에 소개되었습니다. 이것은 패러다임은 UI가 상태에 따라 동작하는것으로 전환되었습니다. 컴포넌트의 상태가 주어지면 React는 어떻게 컴포넌트가 보여야 하는지 결정할 수 있습니다. React는 상태라는 아이디어 위에서 조직되었습니다. 그러나 상태는 React 어플리케이션을 만드는데 있어 가장 어려운 부분으로 여겨지고 있습니다.
React의 상태 관리를 견고한 벨트로 상상해봅시다. 수년동안 이 벨트를 사용해왔고 느리게 새로운 도구들이 추가됩니다. 각각의 도구들은 각각의 목적을 수행합니다. 당신은 볼트를 조일때 망치를 사용하지 않습니다. 장인으로서 각각의 도구를 적절한 상황에 사용해야 합니다.
리액트의 상태관리 도구는 견고한 belt이지만 모든 사람들은 어떤 도구에 언제 접근해야 하는지 이전의 경험을 가지고 있지 않습니다.
이 글은 상태관리의 과거, 현재, 미래를 설명하여 팀, 프로젝트 조직에 정확한 결정을 내릴수 있게 도와줄 것입니다.
Glossary
시작하기 전에, 자주 쓰이는 용어들을 이해하는 것은 매우 중요합니다. 이것들은 표준이름이 아닙니다. 사소한 부분은 다를 수 있지만 아래의 개념은 같습니다.
- UI 상태 - 상태는 어플리케이션의 interactive 부분들을 제어하기 위해 사용됩니다(dark mode toggle, modals)
- Server Cache State – State from the server, which we cache on the client-side for quick access (e.g. call an API, store the result, use it in multiple places).
- 서버 캐시 상태 - 클라이언트에서 빠른 접근을 위해 캐시에 위치한 서버의 상태(API 요청, 결과를 저장, 복수의 위치에서 사용)
- Form 상태 - form의 다양한 상태(로딩, 제출중, 불가능, 검증, 재요청) 제어, 비제어 폼의 상태 또한 있다
- URL 상태 - 브라우저에 의해 관리되는 상태(상품 필터, 쿼리 파라미터 저장, 동일한 필터링된 상품을 보기 위하여 새로고침 할때)
- 상태 Machine - 시간에 따른 상태 명시적 모델( 초록 -> 노랑 -> 빨강으로 변하는 신호등, 그러나 절대 초록 -> 빨강은 되지 않음)
Past
리액트 컴포넌트는 어플리케이션이 구조적이고 재사용이 가능하도록 도와줍니다. 각각의 컴포넌트는 각각의 상태를 가지고 있습니다.
웹앱이 점점 복잡해지면서 컴포넌트간에 로직을 공유하는 것을 더 쉽게 해주는 해결책들이 등장하였습니다.
Timeline
상태관리 도구가 시간을 거치면서 어떻게 변화하였는지 보여주기 위해 리액트에서 유명한 상태관리 도구들의 대략적인 타임라인을 준비하였습니다. 이 리스트는 UI 상태에 초점이 맞춰져 있습니다. 이 목록은 문맥을 제공하기에는 충분합니다.
- 2013 – 소개
- 2014 – Flux (many libraries)
- 2015 – Redux
- 2016 – MobX
- 2018 – Context
- 2019 – Hooks Introduced (+ React Query, SWR)
- 2019 – Zustand
- 2019 – xState
- 2020 – Jotai, Recoil, Valtio
- 2021 – useSelectedContext
타임라인에 나와있는 것들은 배울필요가 있다는 것을 의미하지는 않습니다. 글의 이후에서 리액트의 상태관리 도구 역사를 깊게 살펴보겠습니다.
Redux
Redux는 Flux 아키텍처에 기초하여 탄생하였습니다. Flux 패턴은 2014년 페이스북에 의해 제안되었습니다.
Redux는 2015년에 등장하였고 빠르게 Flux 영감을 받은 라이브러리중 가장 인기가 높아졌습니다. 이것의 도구와 라이브러리들은 UI 상태와 서버 캐싱 상태를 캡슐화 하였습니다. Redux는 여전히 가장 인기있고 널리 쓰입니다.
Server Caching State
React의 초기에는 많은 상태관리 도구가 API로 데이터를 요청하고 어플리케이션 간에 사용을 위하여 캐싱을 하는것으로 요약되었습니다.
사용자들은 Redux는 쉽지 않았고 단지 서버 캐시 상태를 관리하기 위해 널리 쓰였므로 Redux와 같은 라이브러리를 배우는데 많은 노력을 기울였습니다.
React Hook의 발표와 함께, 공통된 훅에 로직을 캡슐화 하는것이 쉬워졌고 접근하기 쉬웠습니다. SWR과 React 쿼리와 같은 라이브러리들이 문제를 해결하기 위해 등장하였습니다.
왜 서버 캐싱 상태를 다루기 위해 별도의 라이브러리를 써야 하는지 의문이 들수 있습니다. 우선 캐싱은 어렵습니다. 서버 상태를 캐싱하는 것은 UI상태와 다른 문제를 해결합니다. 아래의 리스트들은 위에서 언급한 라이브러리들이 다루어 주는것에 대한 리스트 입니다.
- Polling on interval (간격을 두고 계속 요청하는 것)
- Revalidation on focus (focus되었을때 재검증 하는것)
- Revalidation on network recovery (네트워크가 회복되었을때 재검증 하는것)
- Local mutation (Optimistic UI)
- Smart error retrying (에러 재요청)
- Pagination and scroll position recovery (페이지 네이션, 스크롤 위치 기억)
위의 것들을 직접 해결하고 싶다면 아마 불가능 할것이다.
React Context
16.3 버전에서 React Context는 컴포넌트간에 로직을 공유하기위한 first-party 솔루션으로 등장하였다. Context는 연관된 컴포넌트간에 value를 props로서 계속 깊게 넘겨주어야 하는 것을 예방해 주었다.
React Context는 상태관리도구가 아니다. 그러나 useReducer와 같은 hook과 함께 사용하여 상태관리도구가 될 수 있다. 이 조합은 많은 상황에서 UI 상태를 해결해 주었다.
Present
2021년에 React에서 상태를 관리하기위한 다양한 방법이 존재합니다. 다양한 상태를 이해하는 커뮤니티가 성장함에 따라 구체적인 사용사례를 해결하는 보다 세분화된 라이브러리가 생성되었습니다.
State Machines
Switch 상태를 살펴보겠습니다. 만약 state가 어느 경우라도 일치한다면 그에 상응하는 코드가 실행됩니다. 유한한 경우가 존재하게 됩니다. 아래는 단순한 state machine 입니다.
switch (state) {
case state === 'loading':
// show loading spinner
break;
case state === 'success':
// show success message
break;
default:
// show error message
}
유한한 State Machine과 State chart는 컴퓨터과학 개념의 근간입니다. React에 특화된 것이 아닙니다. 서드파티 라이브러리 없이 useReducer를 이용하여 state machine을 사용할 수 있습니다.
State Machine들은 모든 곳에 잘 동작합니다. 데이터베이스, 전자, 자동차 등등 말이죠. 상태관리가 리액트 생태계 안에서 진화함에 따라 우리는 이러한 오래된 개념들이 현대의 상태관리 이슈를 해결할수 있을것으로 인식하였습니다. State Machine들은 form 상태를 해결하기에 가장 일반적입니다.
유한한 State Machine과 함께 우리는 어플리케이션 혹은 컴포넌트의 유한한 상태를 제어할 수 있습니다. 연습에서 State Machine은 정의된 엣지 케이스와 요구사항으로 부터 버그를 밝혀내는데 도움을 줄 수 있습니다. 정보를 더 제공하자면 xState 문서나 이 과정을 시청하길 추천합니다. state machine을 온라인에서 시각화 할 수도 있습니다.
Zustand, Recoil, Jotai, Valtio, Oh My!
React 상태관리에 왜 많은 라이브러리들이 존재할까요?
Figma를 살펴보겠습니다. 컴포넌트가 렌더링 되거나 컴포넌트의 지역 상태의 외부에서 상태를 제어하는 툴바를 확인할 수 있습니다.
상상할 수 있듯이 이러한 규모의 어플리케이션은 복잡한 상태관리 도구를 요구합니다. 성능과 frame 비율은 좋은 사용자 경험을 위해 필수적입니다. 그래서 언제 어떻게 리렌더 해야하는지 제어하길 원할겁니다. 이러한 활용 사례는 상태관리 영역으로의 탐험을 초래하였습니다.
이러한 라이브러리들간에 차이점을 요약해 보겠습니다.
- Valtio uses proxies to provide a mutation-style API
- Jotai is optimized for "computed values" and async actions
- Zustand is a very thin library specifically focused on module state
- Recoil is an experimental library using a data-flow graph
Having complex state doesn't necessarily mean you have to pull for a third-party library. You can start with React and JavaScript and see how far it takes you. If optimizing requires a state management library, you can track that metric (e.g. frame rate), measure it, and verify it solves a real problem.
복잡한 상태는 서드파티 라이브러리를 꼭 써야함을 무조건 의미하지는 않습니다. React와 javascript로도 꽤 많은 것을 할 수 있습니다. 최적화에 상태 관리 라이브러리가 필요한 경우 해당 메트릭(예: 프레임 속도)을 추적하고 측정하여 실제 문제를 해결하는지 확인할 수 있습니다
명백히 필요로 할때까지 이러한 라이브러리들중 선택을 하지 마세요
Immutable State
다른 논쟁거리는 mutable vs immutable 상태 입니다. 정답은 없습니다. 단지 의견일 뿐입니다. 만약에 vanilla javascript로 상태관리를 수행하고 있다면 mutable 상태를 다루고 있을것입니다. 변수를 선언하고 새로운 값과 일치하는지 설정을 할것입니다. let vs const는 이곳에서 논쟁을 확인할 수 있습니다.
Immutable 상태는 리액트와 함께 많은 인기를 얻었습니다. immutable 군중은 선택한 상태 관리 솔루션이 상태를 직접 변형하도록 허용하면 더 많은 버그가 발생한다고 주장합니다. mutable 군중은 복잡성과 trade-off할 가치가 없다고 주장합니다. 직접적인 조작은 비직접적인 조작보다 항상 불안정합니다. 이것은 편의성과 위험성간에 trade-off 입니다.
Immer와 같은 솔루션은 mutable 코드를 작성하고 immutable하게 실행되도록 해줍니다. 기본 idea는 현재 상태의 대리자인 draft 상태를 변화 시키는 것입니다. mutation이 완료되었다면 immer는 draft state의 변화에 근거하여 다음 state를 생성할 것입니다.
URL State
아마존과 같은 e-commerce 사이트를 만들고 있다고 합시다. React 책을 검색하고 별 4개 이상으로 filter 하였습니다. 이 상태는 쿼리 파라미터로 존재하게 되고 브라우저에서 관리됩니다. 만약 페이지를 새로고침하면 동일한 상품 들을 볼수 있습니다. 이 URL을 다른 이들과 공유할 수 있고 그들또한 같은 결과를 볼수 있습니다.
Nomad List와 같은 예시도 있습니다. 브라우저 URL 상태를 데이터 동작에 따라 변경할 수 있습니다.
추가로 사람 친화적인 URL을 만들 수 있습니다.
Future
대규모의 어플리케이션 들에서는 Context를 기초로한 상태관리 도구들(useReducer와 같은)이 과도한 리렌더링을 유발해 문제를 발생시킬수 있습니다. 만약에 상태가 변한다면 useContext를 사용하는 모든 컴포넌트들이 리렌더 됩니다. 이것은 UI의 동작을 느리게 만들수 있습니다. 만약 시각적으로 눈치챌 수 없다면 리렌더를 조사하기 위해 React Dev Tools를 사용할 수 있습니다.
React 팀에서는 useSelectedContext hook을 Context에서의 성능 문제를 해결하고자 제안하였습니다.
2019년 7월에 RFC가 소개되었고 2021년 1월에 작업이 시작되었다고 합니다. 이 hook은 Context의 일부만 선택할 수 있게 하고 일부가 변했을때 일부만 렌더링 되게 합니다.
이미 리렌더 퍼포먼스를 다루기 위한 방법들이 있지만 (useMemo와 같은) Context가 선호되어집니다.
useContextSelector 라이브러리와 같은것들이 있고 Jotai, Formik3는 이러한 맥락아래에서 사용됩니다. useSelectedContext를 React 표준 라이브러리로 가져가는것은 더 나은 성능을 제공하고 외부 라이브러리의 복잡성과 거대한 코드 사이즈를 줄여줄 것입니다.
먼 미래에 React는 자동으로 어떤 컴포넌트가 리렌더 되야하는지 이해할수 있을 것입니다(auto-memoization)
State Management Options
이것은 포괄적인 리스트 입니다. 오픈 소스 이고 만약에 동의하지 않거나 잘못되었다면 PR을 해주세요. 일반적으로 당신의 개발자들이나 팀에 의존되어 있습니다. 리덕스와 함께 행복하다면 유지하여도 됩니다.
Form State
Experience | Learning Appetite | Project/Team Size | Solution |
Beginner | Low | Small | useState |
Beginner | Medium | Medium, Small | Form Library (Formik, Final Form) |
Beginner | High, Medium | Large | Ask your tech lead |
Intermediate | Low | Medium, Small | Form Library (Formik, Final Form) |
Advanced | Medium | Medium | State Machines |
Advanced | High | Medium | State Machines |
Advanced | High | Large | State Machines |
UI State
Experience | Learning Appetite | Project/Team Size | Solution |
Beginner | Low | Small | useState |
Beginner | Medium | Medium, Small | useContext + useReducer |
Beginner | High, Medium | Large | Ask your tech lead |
Intermediate | Low | Medium, Small | Redux Toolkit |
Advanced | Medium | Medium | useContext + useReducer |
Advanced | High | Medium | Jotai, Valtio |
Advanced | High | Large | Recoil (or Relay if you use GraphQL) |
Server Cache State
팀 규모나 경험에 관계 없이 SWR과 React Query는 훌륭한 솔루션 들입니다. 두개중 하나에 만족할 것입니다. 만약 GraphQL을 사용한다면 이미 Apollo에 대해 알고있을 것입니다.
That's all, folks!
리액트에서 상태관리는 지난 8년간 진화해 왔습니다. 거대한 웹 어플리케이션들을 구축하는데 있어 가장 어렵고 미묘한 부분 중 하나입니다. 다양한 유형의 상태들과 그 절충안을 이해하는 것은 정보에 입각한 결정을 하는데 매우 중요합니다. 이 포스트가 도움이 되길 바라고 읽어주셔서 감사합니다.
Past, Present, and Future of React State Management – Lee Robinson
Learn about the history of state management in React and what the preferred solutions are today.
leerob.io
'Frontend > React' 카테고리의 다른 글
React 연속된 요청 useEffect 내에서 처리하기 (useEffect, suspense) (0) | 2022.04.10 |
---|---|
React suspense (1) | 2022.04.10 |
Recoil 🌱 (0) | 2022.04.08 |
React 상태관리🌱외 minor concept (0) | 2022.04.07 |
React css-in-js (0) | 2022.04.06 |