모던 리액트 deep dive 스터디 - 2주차 발표 정리
범위 : 5장
상태관리
웹 애플리케이션에서의 상태란?
=> 어떠한 의미를 지닌 값이며 애플리케이션의 시나리오에 따라 지속적으로 변경될 수 있는 값
UI
웹 애플리케이션에서 상태는 상호 작용이 가능한 모든 요소의 현재 값을 의미한다.
ex) 다크/라이트 모드, input...
URL
브라우저에서 관리되고 있는 상태값
ex) 쿼리 (brandCd="N", gender='M", ...)
Form
로딩중인지, 제출됐는지, 접근이 불가능한지, 값이 유효한지...
서버에서 가져온 값
API 요청의 결과
tearing
하나의 상태에 따라 서로 다른 결과물을 사용자에게 보여주는 현상
tearing을 어떻게 방지할것인가? 에 대한 고민이 든다.
리액트에서의 상태관리
Flux 패턴
페이스북 팀이 양방향이 아닌 단방향을 데이터 흐름을 변경하는 것을 제안하면서 나오게 됨.
양방향의 데이터 흐름일 경우 코드의 양이 많아지고 관리가 어려워진다는 단점이 있음.
액션
어떠한 작업을 처리할 액션과 그 액션 발생 시 함께 포함시킬 데이터를 정의
액션 타입과 데이터를 각각 정의해 이를 디스패처로 보낸다
디스패처
액션이 정의한 타입과 데이터을 스토어에 보내는 역할
스토어
실제 상태에 따른 값과 상태를 변경할 수 있는 메서드가 정의되어 있음.
뷰
리액트의 컴포넌트에 해당. 스토어에서 만들어진 데이터를 가져와 화면을 렌더링
사용자의 입력이나 행위에 따라 액션을 호출할 수도 있다.
단방향 데이터흐름 또한 작성할 코드가 많아지지만, 한 방향으로만 데이터가 흐르므로 흐름을 추적하기 쉽고 코드를 이해하기 수월해진다.
리덕스
최초에는 Flux 구조를 구현하기 위해 만들어진 라이브러리였다.
Elm 아키텍처 영향을 받음
Elm
- 웹페이지를 선언적으로 작성하기 위한 언어
- Flux와 마찬가지로 데이터 흐름을 세 가지(모델, 뷰, 업데이트)로 분류하고, 단방향의 데이터 흐름을 가짐
리덕스가 리액트 생태계에 준 영향
- props drilling 해결
- connect만 쓰면 스토어에 바로 접근가능
보일러 플레이트가 많은게 단점
Context API
props drilling 문제와 리덕스의 보일러 플레이트 문제가 있었던 와중에 Context API가 나오게 되었음.
- props drilling 문제 해결
- 상태 관리를 위한 것이 아닌 상태 주입을 위한 기능
- 렌더링을 막아주는 기능은 없어 주의 필요
React-Query, SWR
외부에서 데이터를 불러오는 fetch를 관리하는 데 특화된 라이브러리 (HTTP 요청에 특화되어있음)
- 상태 관리 라이브러리의 일종
리덕스에만 의존하던 시대와는 달리 리액트 훅의 등장으로 다른 상태 관리법들이 등장하게 되었음.
리액트 훅으로 시작하는 상태 관리
useState, useReducer 만으로는 상태 관리의 한계가 있다.
- 컴포넌트별로 값이 초기화되므로 컴포넌트에 따라 서로 다른 상태를 가짐.
- 지역 상태(useState를 기반으로 한 상태)는 해당 컴포넌트에서만 유효함.
지역상태에서 관리되는 것이 아닌 완전히 다른 곳에서 값을 공유해줄 수는 없을까?
시도는 해볼 수 있지만, 리렌더링이 문제.
1. 컴포넌트 외부 어딘가에 상태를 두고 여러 컴포넌트가 같이 쓸 수 있어야함.
2. 외부에 있는 상태를 사용하는 컴포넌트는 상태의 변화를 알아채고 리렌더링이 일어나야함. 해당 상태를 참조하는 모든 컴포넌트에서 동일하게 동작 해야함.
3. 상태가 원시값이 아닌 객체인 경우에 그 객체에 내가 감지하지 않는 값이 변한다 하더라도 리렌더링이 발생해서는 안됨.
상태 관리 라이브러리
상태 관리 라이브러리는 공통적으로 다음의 특징을 갖는다.
- 지역 상태에서 벗어나 컴포넌트 외부 어딘가에 상태를 둔다.
- 이 외부의 상태 변경을 각자의 방식으로 감지하여 컴포넌트의 렌더링을 일으킨다.
비교적 최근에 나온 Recoil, Jotai, Zustand에 대해 알아보자.
Recoil
정식으로 출시한 라이브러리가 아닌 실험적으로 개발되고 운영되는 라이브러리
- 실제 프로덕션에 사용하기에는 안정성이나 성능, 사용성 등을 보장할 수 없음.
- 현재까지도 정식 출시 안됨. 기능 last merge가 2년전... (2024.11.16 기준)
https://github.com/facebookexperimental/Recoil
RecoilRoot
- 애플리케이션의 최상단에 선언
- 상태값을 저장하기 위한 스토어를 해당 영역에서 생성
- 스토어의 상태값에 접근할 수 있는 함수들이 있으며, 이 함수를 활용해 상태값에 접근하거나 상태값을 변경할 수 있음.
- 값의 변경이 발생하면 이를 참조하고 있는 하위 컴포넌트에 모두 알림.
atom
- Recoil의 최소 상태 단위
- 다른 atom과 구별되는 key 값을 필수로 가짐
- default는 atom의 초깃값을 의미
useRecoilValue
- atom의 값을 읽어오는 훅
useRecoilState
- useState와 유사하게 값을 가져오고, 이 값을 변경할 수도 있는 훅
Recoil 요약
1. 애플리케이션의 최상단에 RecoilRoot를 선언해 하나의 스토어를 만듬.
2. atom이라는 상태 단위를 RecoilRoot에서 만든 스토어에 등록
3. 컴포넌트는 Recoil에서 제공하는 훅을 통해 atom의 상태변화를 구독
4. 값이 변경되면 forceUpdate 같은 기법을 통해 리렌더링을 실행해 최신 atom 값을 가져옴.
Recoil 특징
- 메타 팀에서 주도적으로 개발되기 때문에 새로운 리액트의 기능에 대해 잘 지원할 것으로 기대됨. 언제쯤...?
- selector를 필두로 다양한 비동기 작업을 지원하는 API 제공, redux-saga나 redux-thunk와 같은 추가적인 미들웨어를 사용하지 않아도 비동기 작업 처리 가능.
- Recoil 자체적인 개발 도구 지원
Jotai
Recoil의 atom 모델에 영감을 받아 만들어진 상태 관리 라이브러리
- bottom-up 접근법을 취함.
- Context의 문제점인 불필요한 리렌더링을 해결하고자 설계 됨.
Recoil에 비해 최근까지 꾸준히 업데이트 중
https://github.com/pmndrs/jotai
atom
- 최소 단위의 상태, Recoil과는 다르게, atom 하나만으로도 상태를 만들수도, 또 이에 파생된 상태를 만들 수 있음.
- 별도의 key를 넘겨주지 않아도 됨. => 객체 그 자체를 키로 활용해 값을 저장하기 때문.
- Jotai는 atom에 따로 상태를 저장하지 않음.
useAtomValue
- useReducer에서 [version, valueFromReducer, atomFromReducer]를 반환함.
이는 각각 [store의 버전, atom에서 get을 수행했을때 반환되는 값, atom 그 자체] 를 의미
- atom의 값은 store에 존재
- rerenderIfChanged는 아래의 경우에 해당될 때 수행된다.
1. 넘겨받은 atom이 Reducer를 통해 스토어에 있는 atom과 달라지는 경우
2. subscribe를 수행하고 있다가 어디선가 이 값이 달라지는 경우
리렌더링을 일으키는 rerenderlfChanged 메서드 덕분에 useAtomValue로 값을 사용하는 쪽에서는 언제든 최신 값을 렌더링 할 수 있음.
useAtom
- useState와 동일한 형태의 배열을 반환
- atom의 현재 값을 나타내는 useAtomValue 훅의 결과를 반환
- useSetAtom 훅을 반환, atom을 수정할 수 있는 기능을 제공
Jotai의 특징
- Recoil과 유사하면서도 Recoil의 한계점을 극복하려는 노력이 보임
- Recoil의 atom 개념을 도입하면서 API가 간결해짐.
- 객체의 참조를 통해 값을 관리하기 때문에 키를 별도로 관리할 필요가 없음.
- selector 없이도 atom만으로 atom 값에서 또 다른 파생된 상태를 만들 수 있음.
- Recoil의 정식 버전이 출시 되지 않아 사용을 망설이는 개발자들이 많이 사용하고 있음.
- 최근까지도 활발한 업데이트 중 (2024.11.16 기준)
Zustand
리덕스에 영감을 받아 만들어짐.
partial
- state의 일부분만 변경하고 싶을 때 사용
replace
- state를 완전히 새로운 값으로 변경하고 싶을때 사용
getState
- 클로저의 최신 값을 가져오기 위해 함수로 만들어짐.
subscribe
- listener를 등록함. Set 형태로 선언되어 중복 관리가 용이함.
destroy
- listener를 초기화하는 역할
createStore는 getState, setState, subscribe, destroy를 반환하는데, vanilla.ts에서는 이 createStore만을 export하고 있다.
=> store가 완전히 독립적으로 구성되어 있어, 순수하게 자바스크립트 환경에서도 사용 가능하다.
리액트 환경
useStore
- store를 읽고 리렌더링할 수 있게 해줌.
create
- 스토어를 만들어주는 변수
Zustand 특징
- 많은 코드를 작성하지 않아도 빠르게 스토어를 만들고 사용할 수 있다.
- 가볍게 쓸 수 있다는 점이 리덕스와 비교되는 장점
- 용량 또한 Recoil, Jotai에 비해 적음
- 타입스크립트 기반
- 미들웨어를 지원한다. ex) persist, immer, ...
각 라이브러리 별로 특징을 잘 파악하여, 현재 애플리케이션 상황에 맞는 라이브러리를 골라 사용해보자.
특히, 메인테이너가 많고 다운로드가 활발하며 이슈가 잘 관리되고 있는 라이브러리일수록 좋다.
리액트의 방향성에 맞춰서 라이브러리도 발맞춰 대응해주는 라이브러리가 적합해보인다.