일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- react-query
- 리액트네이티브
- Cache
- SWC
- CSS-in-JS
- 컴포넌트
- next hydration
- mockoon
- React
- styled-component
- JavaScript
- 쓰레드
- 동기
- 비동기
- 개발자
- react server component
- 목킹
- link
- mock service worker
- next.js
- front-end mocking
- 리액트쿼리
- 최적화
- 기초
- Concurrent Mode
- react-native
- 리액트
- Critical Rendering Path
- 자바스크립트
- Babel
- 캐쉬
- 기본
- sprinkles
- thread
- MSW
- async
- 아키텍처
- Basic
- amplify
- vanilla-extract
- Today
- Total
Don’t worry about failures
전역 상태관리에 대해 본문
전역 상태관리를 위해 많은 도구들이 사용된다.
recoil, redux, mobx, context 등..
전역 상태관리에 대해 간단히 알아보고 도구들에 대해 알아보자.
리액트의 경우 컴포넌트를 대부분 작은 단위로 나누어 구조를 구성한다. 부모의 자식, 자식의 자식... 과 같은 구조를 가지게 되고, 상태값은 단방향으로 부모에서 자식에게만 값을 전달이 가능하다.
여기서 자식은 상태를 받기 위해 props를 통해 전달받게 된다. 간단한 구조에 있어 props를 통해 전달받는 것은 문제가 되지 않을 것이다.
반면, 복잡하고 자식의 깊이가 깊다라고하면 props를 자식의 자식의 자식의 자식까지 계속 전달해줘야한다. 이러한 것을 props drilling라고 한다.
props를 통해 받는 상태의 단점은 뭘까?
가장 큰 것은 유지 보수가 힘들다는 것이다. 만약 자식의 자식이 10개가 있다고 해보자. 그렇게되면 props는 부모부터 자식까지 모두 기입을 해줘야한다. 여기에 더해 만약 naming이 변하게 된다고 하면 모두 변경해줘야하는 번거로움이 존재한다.
props drilling의 문제를 해결하고자 나온 개념이 전역 상태라는 것이다.
하나하나 넘겨주는 것이 아닌 전역으로 관리되는 값으로 어느 컴포넌트에서도 접근할 수 있게 해주는 것이다.
종류에 대해 알아보자.
1. React Conext
리액트 context는 리액트 내장된 도구이다. 별도의 라이브러리 설치없이 간단하게 사용할 수 있다.
예시) https://velog.io/@velopert/react-context-tutorial 발췌
import { createContext, useContext, useMemo, useState } from 'react';
const CounterContext = createContext();
function CounterProvider({ children }) {
const [counter, setCounter] = useState(1);
const actions = useMemo(
() => ({
increase() {
setCounter((prev) => prev + 1);
},
decrease() {
setCounter((prev) => prev - 1);
}
}),
[]
);
const value = useMemo(() => [counter, actions], [counter, actions]);
return (
<CounterContext.Provider value={value}>{children}</CounterContext.Provider>
);
}
function useCounter() {
const value = useContext(CounterContext);
if (value === undefined) {
throw new Error('useCounterState should be used within CounterProvider');
}
return value;
}
function App() {
return (
<CounterProvider>
<div>
<Value />
<Buttons />
</div>
</CounterProvider>
);
}
function Value() {
const [counter] = useCounter();
return <h1>{counter}</h1>;
}
function Buttons() {
const [, actions] = useCounter();
return (
<div>
<button onClick={actions.increase}>+</button>
<button onClick={actions.decrease}>-</button>
</div>
);
}
export default App;
createContext를 통해 context를 생성하고 내부에 있는 Provider를 통해 value값을 저장한다. 사용시에는 useContext를 통해 값을 꺼내올 수 있다.
react context의 경우 특징은 Conext의 상태가 변경이 된다고하면 즉 provider에 제공되는 value가 변경이 된다하면 Provider 안에 있는 모든 컴포넌트들이 리렌더링이 발생한다.
이러한 특징을 가지고 있기 때문에 사용에 주의해야하며, 리액트 문서에서 use case를 제공한다.
1. Theming
2. Current account
3. Routing
4. Managing state
위와 같은 use case를 가이드하고 있으며, 상태 값을 애플리케이션 전반적으로 전달하고자 할 때 사용된다.
2. Redux
리덕스는 다른 라이브러리보다 오래된 상태관리 라이브러리이다.
리덕스의 핵심 개념은 Store, Action, Reducer이다.
Store : 애플리케이션의 전역상태를 보관하는 객체. Redux 애플리케이션은 단 하나의 스토어만 가진다.
(중앙 집중적)
Action : 상태 변화를 일으키는 일이 발생했음을 알리는 객체. 액션은 'type' 필드를 반드시 가지며, 이외에 상태 업데이트를 수행할 때 필요한 추가 데이터를 포함할 수 있다.
Reducer : 액션을 받아 이전 상태를 다음 상태로 변환하는 순수함수. 리듀서는 (previouseState, action) => newState의 형태를 가진다.
위의 개념들이 어떻게 흐름을 가지는지 봐보자.
리듀서는 단방향 흐름을 가진다.
1. Action발생 : 애플리케이션에서 어떤 이벤트가 발생하면, 관련 액션을 dispatch() 함수를 사용하여 발송.
2. Reducer 실행 : 스토어는 발송된 액션을 받아 리듀서 함수를 실행한다. 리듀서는 현재 상태와 액션을 기반으로 새로운 상태를 계산.
3. 상태 업데이트 : 리듀서가 새로운 상태를 반환하면, 스토어의 상태가 업데이트된다.
4. UI 반응 : 상태가 변경되면, 이에 응답하여 UI 업데이트
리덕스는 이와 같은 구조를 가지고 있다.
이러한 구조에 따라 장,단점이 발생한다. 이에 대해 봐보자.
장점
- 단방향 흐름이 명확하고, 각 개념의 구조가 잡혀있기 때문에 예측 가능한 상태관리를 할 수 있다.
- 디버깅 용이. Redux DevTools와 같은 도구를 사용하면 애플리케이션의 상태 변화를 시간에 따라 추적하고, 이전 상태로 돌아가거나 변화를 취소할 수 있다.
- 유지보수 좋음. 명확한 구조와 일관된 데이터흐름에 대규모 애플리케이션에서 장점을 보인다.
위와 같은 정점을 보유하고 있으며, 이는 구조에 따른 장점이 가장 크다고 볼 수있다.
하지만 단점도 존재한다.
구조가 명확한 만큼 구조에 있으 신경을 많이 써야한다는 것이다. 이에 따라 보일러플레이트 코드가 존재하며, 반복적인 코드가 발생할 수 있다. 이를 줄이기 위해 Redux Toolkit과 같이 사용할 수있다.
이와 같이 리덕스는 여러 라이브러리와 함께 사용하여, 단점을 보완하는 등의 작업들이 필요하다.
3. Recoil
리코일은 가장 최근에 릴리즈된 라이브러리이다. 이는 페이스북 내부에서 시작되었다.
다른 라이브러리에 비해 진입 장벽이 매우 낮고, 간편하게 사용할 수 있다.
Recoil의 장점은 간편한 것도 있지만, 페이스북 자체 내에서 개발되고 있기 때문에 가장 리액트에 맞게 구성된 라이브러리가 될 수 있다라는 것이다.
이와 같은 장점은 리코일 공식문서에도 노출이 된다. 호환성 단순함을 위해 외부 라이브러리보단 자체 내장된 Context를 활용하면 좋으나, 한계의 존재가 있다. 이를 보완하고자 recoil은 내장되지는 않았지만 가장 리액스럽게 호환되도록 개발이 되고 있다.
특히나, 동시성 모드를 호환하고자 하고 이는 다른 라이브러리에 비해 더 좋은 성능을 발휘할 수 있을 것이라 생각이 든다.
주요 개념
Atom : 하나의 상태. atom을 구독하고 있는 컴포넌트들은 atom이 변경하게 되면 리렌더링이된다.
Selector : 상태에서 파생된 데이터로, 다른 atom에 의존하는 동적인 데이터를 만들 수 있게 해준다.
간단 예시 . https://ui.toast.com/weekly-pick/ko_20200616에서 발췌
// 동물 목록 상태
const animalsState = atom({
key: 'animalsState',
default: [{
name: 'Rexy',
type: 'Dog'
}, {
name: 'Oscar',
type: 'Cat'
}],
});
// 필터링 동물 상태
const animalFilterState = atom({
key: 'animalFilterState',
default: 'dog',
});
// 파생된 동물 필터링 목록
const filteredAnimalsState = selector({
key: 'animalListState',
get: ({get}) => {
const filter = get(animalFilterState);
const animals = get(animalsState);
return animals.filter(animal => animal.type === filter);
}
});
// 필터링된 동물 목록을 사용하는 컴포넌트
const Animals = () => {
const animals = useRecoilValue(filteredAnimalsState);
return animals.map(animal => (<div>{ animal.name }, { animal.type }</div>));
}
또한 기능 중의 하나인 atomFamily를 통해 동적으로 atom을 만들고 목록을 더 효율적으로 관리할 수 있다.
일단 먼저 atom으로만 상태관리를 해보자.
예시 ) https://blog.rhostem.com/posts/2021-07-07-state-management-with-recoil-atomfamily 발췌
type Todo = {
id: string;
title: string;
isDone: boolean
}
type TodoList = Todo[]
const todoListState = atom<TodoList>({
key: 'todoListState',
default: [],
})
하나의 atom으로 관리하면 하나의 아톰에 목록으로 값을 저장한다.
이렇게 되면 목록 중 하나의 값만 변경하게 되도, 새로운 배열로 업데이트가 되어야한다.
이를 보완하고자 atomFamily의 개념이 존재한다.
atom이 목록을 전체 관리하기보단 목록에서 각각의 한가지 '할일' 컴포넌트가 하나의 atom을 구독하고 관리한다.
아래와 같이 하고 싶은 것이다.
const TodoComponent = () => {
const [todo, setTodo] = useRecoilState(동적으로 만들어진 atom)
}
이를 atomFamily를 통해 구현해보자.
const todoItemState = atomFamily<Todo, string>({
key: 'todoItemState',
default: (id) => {
return {
id,
title: '',
isDone: false,
};
},
});
const TodoComponent = (id: string) => {
const [todo, setTodo] = useRecoilState(todoItemState(id))
return (
<div>{todo.title}</div>
)
}
이와 같이 특정 ID를 통해 컴포넌트에 해당하는 atom을 가져와서 사용할 수 있다. 이를 통해 각각의 아톰으로 독립적으로 관리할 수 있게 됐다.
recoil은 이와 같은 사용에 있어 쉽고, 리액트스럽운 장점을 가지고 있다.
하지만 단점으로는 아직은 완전한 상태가 아니라는 것이다. 최신 버전이 작업일 기준으로 0.7.7로 메이저 버전이 나오지 않은 상태이다.
4. MobX
MobX 또한 Recoil과 같이 사용법에 있어 간단하게 사용이 가능하다. MobX 공식문서 또한 간단하면서 뛰어난 확정성을 가진 상태 관리 솔루션이라고 소개를 한다.
MobX의 경우 Reactive Programming으로부터 영감을 받았다고한다. 이처럼 MobX는 구독 개념을 통해 변경되는 state를 바라보고 변경되면 리렌더링을 통해 UI에 반영되도록한다. 이를 도식화한 것을 보면
액션을 통해 상태를 변경하고 해당 변경된 값을 리렌더링을 시킨다.
코드로써 예시를 봐보자
class NumStore {
num = 0;
constructor() {
makeObservable(this, {
num: observable,
add: action,
isOver10: computed,
})
}
add() {
this.num += 1;
}
get isOver10() {
return num > 10;
}
}
num는 상태를 의미한다.
add는 action이고 상태를 변경하는 하나의 메소드이다.
isOver10은 computed로써 state의 파생된(derived)된 값으로 볼 수 있다. 이는 state를 변경하는 것이 아닌 state를 통해 새로운 파생된 값을 도출하는 값으로 보면 된다.
이렇게 MobX는 각각의 값에 대한 기능을 명시하고 사용할 수 있다.
이 스토어를 이제 컴포넌트에서 observer를 통해 구독을 하게되면, 해당 컴포넌트는 스토어의 상태가 변할 때 리렌더링이 발생되어 변경된 값이 반영된다.
구조는 위와같고 state를 내부적으로 관리를 해주고, 최적화 작업까지 진행해주기 때문에 사용하는 개발자 입장에서는 편리한 도구가 될 것이다.
가장 큰 장점은 아키텍처를 구상할 수 있다는 개인적인 생각이다. 편한걸로 생각해보면 Recoil이 더 편할 수도 있고 비슷할 수도 있다.
MobX의 경우 위의 예제에서 봤듯이 객체 개념으로써 접근되며, observable, action, computed와 같이 상태에 대한 흐름을 파악하기 더 쉽게 구성할 수 있으며, 비즈니스 로직을 UI 컴포넌트에서 분리를 시켜줌으로써 아키텍처를 그려나갈 수 있는 것이 장점이라고 생각된다.
간단하게 상태관리에 대해 살펴보고 그와 관련된 도구들을 살펴보았다.
결론적으로는 각 서비스 규모, 개발의 편의성, 개발 성향 등을 고려해서 각각의 맞게 선택하는게 좋을거같다.
참고:
https://react.dev/learn/passing-data-deeply-with-context
Passing Data Deeply with Context – React
The library for web and native user interfaces
react.dev
https://velog.io/@velopert/react-context-tutorial
다른 사람들이 안 알려주는 리액트에서 Context API 잘 쓰는 방법
여러분, 리액트로 웹 애플리케이션 개발 하면서 Context API를 어떻게 사용하고 계신가요? 과거에도 관련 포스트를 작성한적이 있긴 하지만, 지난 몇 년간 Context를 사용하면서 습득하게된 팁들을
velog.io
https://ui.toast.com/weekly-pick/ko_20200616
Recoil - 또 다른 React 상태 관리 라이브러리?
많은 React 상태 관리 라이브러리들이 있고, 가끔 새로운 라이브러리가 등장한다. 그러나 페이스북에서 직접 상태 관리 솔루션을 소개하는 것은 흔하지 않다. 이 라이브러리가 어떤 장점이 있고
ui.toast.com
https://blog.rhostem.com/posts/2021-07-07-state-management-with-recoil-atomfamily
blog.rhostem.com
프론트엔드 웹 개발 기술 블로그
blog.rhostem.com
https://ko.mobx.js.org/README.html
MobX에 대하여 · MobX
<img src="https://mobx.js.org/assets/mobx.png" alt="logo" height="120" align="right" />
ko.mobx.js.org
Web: MobX의 장점과 기본 원칙
전역 상태 관리 라이브러리 MobX의 기본적인 동작 원칙과, Redux와 비교했을 때 가지고 있는 장점에 대해 알아보자
medium.com
'React' 카테고리의 다른 글
리액트 서버 컴포넌트 (0) | 2024.04.04 |
---|---|
MSW에 대해 (0) | 2024.03.26 |
Hydrate에 대해 (0) | 2024.03.22 |
React StrictMode에 대해 (0) | 2024.03.21 |
useTransition에 대해 (0) | 2024.03.16 |