목차
· 상태 관리 라이브러리 포스팅을 들어가며
· Redux에 대해서 알아보자
· Redux는 세가지 원칙을 가지고 있다.
· Redux는 왜 이런 공식을 통해 상태 관리를 진행할까?
· Redux는 어떻게 상태를 관리할까?
· Recoil에 대해서 알아보자
· Recoil의 Atom이란?
· Recoil의 Selector란?
· Recoil의 전역 상태 관련 Hooks
· Recoil의 특징
· 레퍼런스
상태 관리 라이브러리 포스팅을 들어가며
Recoil의 경우 다양한 기업에서 편안한 사용성으로 인해 Recoil을 많이 사용하고 있으며, Redux 처럼 다양한 구성(action, reducer 등)과 같은 환경 세팅을 할 필요가 없으며 특히 비동기 요청이 매우 심플했다. 또한, 간단한 인터페이스를 통해 전역 상태의 설정 및 정의가 매우 쉬우며 Recoil이 지원하는 Hooks로 이를 get/set 하기 때문에 React 문법과 매우 유사했다. 전역 상태를 사용하기 위한 보일러 플레이트 양이 현저하게 적으며, 상태 정의가 증분 및 분산되어 코드 스플리팅에도 장점을 가지고 있기 때문에 보다 가벼운 프로젝트 진행을 위해 Recoil을 채택하여 프로젝트를 진행하고 싶어 이렇게 포스팅을 진행하게 되었다.
Redux에 대해서 알아보자
리덕스란 자바스크립트 상태관리 라이브러리 중 하나로, 프로젝트 규모가 커질수록 컴포넌트 갯수가 많아질 것이고 그에 따라 관리해야하는 사태들은 자연스레 많아질 수 밖에 없다. 기능이 확장되면서 연관되는 컴포넌트들이 많아지고, 그 안에서 사용되는 상태값들도 복잡해지기 마련이다. 그래서 개발자들은 상태 관리 라이브러리를 사용하게 되고, 그 중 하나가 Redux인 것이다.
Redux는 세 가지 원칙을 가지고 있다.
1. Single source of truth
· 동일한 데이터는 항상 같은 곳에서 가지고 온다.
· 즉, store라는 하나뿐인 데이터 공간이 있다는 의미이다.
2. Changes are made with pure functions
· 변경은 순수함수로만 가능하다.
· Reducer와 연관되는 개념이다. (Store - Action - Reducer)
Store
· 스토어는 상태가 관리되는 오직 하나의 공간이다.
· 컴포넌트와는 별개로 스토어라는 공간이 있어서 그 스토어 안에 앱에서 필요한 상태를 담는다.
· 컴포넌트에서 상태 정보가 필요할 때 스토어에 접근한다.
Action
· 앱에서 스토어에 운반할 데이터를 말한다 (주문서 정도라고 생각해봐도 좋을 것 같다)
· 상태 값을 바꾸는 방식
· 자바스크립트 객체 형식으로 구성되어있다.
{ type: 'TEST', // 필수 payload: { // 옵션 name: '홍길동', age: 100 } }
Reducer
· 액션을 스토어에 바로 전달하는 것이 아니다.
· 액션을 리듀서에 전달해야한다.
· 리듀서가 주문(Action)을 보고, 스토어의 상태를 업데이트하는 것이다.
· Action의 결과로 상태 값을 어떤식으로 바꿀지 구체적으로 정의하는 부분
· 액션을 리듀서에 전달하기 위해 dispatch() 메소드를 사용해야한다.
∴ 정리
1) Action(액션) 객체가 dispatch() 메소드에 전달된다.
2) dispatch(액션)을 통해 Reducer을 호출한다.
3) Reducer은 새로운 Store을 생성한다.
Redux는 왜 이런 공식을 통해 상태 관리를 진행할까?
· 데이터가 단 방향으로만 진행되어야하기 때문이다.
Redux는 상태를 어떻게 관리할까?
· 액션은 액션 type과 전송할 데이터(payload)로 이루어진 객체 형태이다.
· Action Creator은 액션을 반환하는(만드는) 함수이다. 이를 통해 액션을 dispatch한다.
· dispatch는 액션 값을 매개변수로, Reducer을 호출하는 것이다.
· Reducer은 현재(이전)의 state와 받은 action에 따라 그 state를 변화시킨 다음 새로운 state를 반환한다.
· Reducer에서는 상태를 변화시킬 때 반드시 이전 객체와는 다른 새로운 state 객체를 반환해야한다. 이렇게 되면 데이터(상태)가 변화하기 이전과 이후의 객체는 서로 다른 객체가 된다.
· 따라서 이전 상태 객체는 유지하였으므로 불편성을 지킨 것이며, 리덕스는 데이터가 변경되었다는 사실을 Shallow Equality Checking을 통해 상태가 변화했음을 알 수 있다.
∴ 뷰에서 어떤 이벤트를 통해 Action Creator 호출 - Action 생성 - Action dispatch - Reducer에서 이전 state 값과 action에 따라 새로운 state 반환 - view에 새로운 데이터 제공
Redux는 Shallow Equality Checking을 통해 상태의 변화를 감지한다.
· 서로 다른 객체(a, b)인 경우 단순히 a === b 인지만 체크해도 다른지 알 수 있으며, 이것을 Shallow Equality 검사라고 한다.
· 반대로 Deep Equality 검사는, 두 객체 (a, b) 내부 프로퍼티까지 모두 같은지 일일이 체크하는 것을 말한다.
· 따라서 두 가지 방식 중 성능적으로 당연히 Shallow Equality가 우위일 수 밖에 없다.
· 그러므로 Reducer에서는 상태를 변화시킬 때 이전 상태 객체의 값을 변경시키는 것이 아니라, 아예 새로운 객체를 반환하는 것이다. 그리고 여기서 이전 상태 객체는 변경하지 않았으므로 불변성을 지켰다고 하는 것이다.
Redux의 특징
· 단방향 데이터 흐름
· 상태 관리에서 불변성 유지가 매우 중요하기 때문에, 상태를 읽기 전용으로 취급한다.
· 여러 라이브러리를 함께 사용하는 경우가 있기 때문에 러닝 커브가 높은 편이다.
· 액션 하나를 추가하는데 작성이 필요한 부분이 많고, 컴포넌트와 스토어를 연결하는 필수적인 부분들이 존재해 코드량이 많아질 수 있다.
Recoil에 대해서 알아보자
리코일이란 context API 기반으로 구현된 함수형 컴포넌트에서만 사용 가능한 페이스북에서 개발한 라이브러리이다. 호환성이나 단순함을 위해선 React에 내장된 상태 관리 기능을 사용하는 것이 가장 바람직하다. 예를 들어 Hooks나 Context API를 사용하여 상태 관리를 할 수 있는데, 그런 경우에 여러가지 한계가 존재한다.
· 컴포넌트 상태를 공통된 상위 컴포넌트까지 끌어올려 공유할 수 있지만, 이 과정에서 거대한 트리가 리렌더링 되기도 한다.
· Context API는 단일 값만 저장 가능하고, 여러 값들의 집합을 담는 것은 불가하다.
· 위 특성으로 인해 상태가 존재하는 곳부터 상태가 사용되는 곳까지 코드 분할이 어렵게 된다. 이러한 상황에서 리코일은 리액트스러움을 유지하며 개선하는 방식의 라이브러리이다. 리코일은 방향 그래프를 정의하고 리액트 트리에 붙이는데, 이 그래프의 뿌리(atom)으로 부터 순수함수(selector)를 거쳐 컴포넌트로 흐른다.
· 공유되는 상태도 리액트의 로컬 상태처럼 간단하게 get/set 인터페이스가 가능하도록 API가 제공된다.
· 상태 정의는 증분 및 분산되어 코드 스플리팅이 가능하다.
Recoil의 Atom 이란?
리코일에서 상태의 단위를 의미하며 업데이트 및 구독이 가능하다. Atom이 업데이트되면 각각의 구독된 컴포넌트튼 새로운 값을 반영하여 리렌더링 된다. Atom은 리액트의 로컬 상태 대신 사용할 수 있다. 동일한 Atom이 여러 컴포넌트에서 사용되는 경우 모든 컴포넌트는 상태를 공유한다. Atom에는 고유한 키가 필요하고, 이 키는 전역적으로 고유해야한다. React state 처럼 디폴트 값 또한 가진다. 컴포넌트에서 Atom을 읽고 쓸 때는 useRecoilState라는 훅을 통해 사용할 수 있다. 이것은 리액트의 useState와 비슷하나 상태가 컴포넌트 간에 공유될 수 있다는 점에서 차이를 가지고 있다.
Recoil의 Selector란?
셀렉터는 Atom 혹은 다른 셀렉터 상태를 입력 받아 동적인 데이터를 반환하는 순수 함수이다. 상태 값에서 비롯된 파생된 데이터를 만들 때 사용되며, Atom 처럼 컴포넌트가 이를 구독할 수 있다. 셀렉터가 참조하던 다른 상태가 변경되면 이도 같이 업데이트되며, 이때 셀렉터를 바라보던 컴포넌트들이 리렌더링 되는 것이다. 셀렉터를 설정할 때도 리코일의 selector() 메서드를 통해 등록하면 된다. 기본적으로, key와 get 2개의 프로퍼티를 설정한다.
· key : 고유한 key 값
· get : Selector 순수 함수, 사용할 값을 반환하며 매개변수인 콜백 객체 내 get() 메서드로 다른 atom 혹은 셀렉터를 참조한다.
export const memoListSelector = selector<MemoList>({
key: 'memoList',
get: async ({ get }) => {
const id = get(focusLabelState)?.id;
const totalList = get(totalMemoListSelector);
return id ? getMemoListByLabel(id) : totalList;
}
})
위의 코드 예제는 메모 목록 배열 값을 반환하는 셀렉터 예제이다. id는 포커스된 라벨의 아이디(atom)을, totalList는 전체 메모리스트(셀렉터)를 각각 가져온 뒤, 아이디가 있으면 라벨 아이디에 해당하는 메모 리스트를, 없다면 전체 메모리스트를 동적으로 반환하는 셀렉터인 것이다. 이렇게 만들었을 때의 장점은 focusLabelState의 아이디가 바뀔 때마다 이 셀렉터도 동적으로 해당하는 리스트를 반환해주는 부분이다. 셀렉터에 set을 설정하게 되면 쓰기 가능한 모드인 셀렉터로도 사용이 가능하다.
Recoil의 전역상태 관련 Hooks
· useRecoilState() : useState() 와 유사하다. [state, setState] 튜플에 할당하며, 인자에 Atoms(혹은 Selector)를 넣어준다.
· useRecoilValue() : 전역상태의 state 상태값만을 참조하기 위해 사용된다. 선언된 변수에 할당하여 사용하면 된다.
· useSetRecoilState() : 전역상태의 setter 함수만을 활용하기 위해 사용된다. 선언된 함수변수에 할당하여 사용하면 된다.
· useResetRecoilState() : 전역상태를 default(초기값)으로 Reset 하기 위해 사용된다. 선언된 함수변수에 할당하여 사용하면 된다.
// 예시
import { useRecoilState, useSetRecoilState, useResetRecoilState } from 'recoil';
import { countState } from '../../recoil/count';
function ReadWriteCount() {
const [ count, setCount ] = useRecoilState(countState); // useRecoilState 을 통한 value, setter 반환
const countValue = useRecoilValue(countState); // 구독하는 atom 의 값만 반환
const setCountUseSetRecoilState = useSetRecoilState(countState); // 값을 변경하는 함수만 반환
const resetCount = useResetRecoilState(countState); // 설정된 기본값으로 리셋
return (
<div>
<h2>읽기 쓰기 카운트 컴포넌트</h2>
<p>카운트 {count}</p>
<p>카운트(useRecoilValue 사용) {countValue}</p>
<button onClick={() => setCount(count + 1)}>숫자 증가</button>
<button onClick={() => setCount(count - 1)}>숫자 감소</button>
<button onClick={() => setCountUseSetRecoilState(count + 1)}>숫자 증가 (useSetRecoilState 사용)</button>
<button onClick={() => setCountUseSetRecoilState(count - 1)}>숫자 감소 (useSetRecoilState 사용)</button>
<button onClick={resetCount}>카운트 리셋</button>
</div>
);
}
export default ReadWriteCount;
Recoil 특징
· 비동기 처리를 기반으로 작성되어 Redux와 같이 다른 비동기 처리 라이브러리에 의존할 필요가 없다.
· 동시성 모드를 제공한다.
· 흐름이 여러 개가 존재하는 경우이다, 리액트에서 렌더링의 동작 우선 순위를 정하여 적절한 때에 렌더링을 해준다.
· Atom - Selector를 거쳐 컴포넌트로 전달되는 하나의 데이터 플로우를 가지고 있어, 복잡하지 않는 상태 구조를 가지고 있다.
· Atom과 Selector만 알고도 어느 정도 구현이 가능하기 때문에 Redux보다 러닝 커브가 비교적 낮다.
· Store과 같은 외부 요인이 아닌 React 내부 상태를 활용하고 context API를 통해 구현되어있기 때문에 리액트에 더 가까운 라이브러리라고 할 수 있다.
레퍼런스
'개발적인' 카테고리의 다른 글
자바스크립트 패키지 매니저, PNPM 란? (0) | 2023.11.21 |
---|---|
웹앱을 위한 PWA 구현하기 - 1 (0) | 2023.11.14 |
PWA의 핵심, 서비스 워커가 뭘까? (0) | 2023.11.12 |
프론트엔드의 뜨거운 감자, 리액트 쿼리 (0) | 2023.10.30 |
디프만 준비해보기 (두둥-탁) (0) | 2023.10.19 |
댓글