Redux란
A state management system for cross-component or app-wide state
컴포넌트 간 상태 또는 앱 전역 상태를 위한 상태 관리 시스템
상태는 3가지로 나눌 수 있다.
1. 로컬 상태: 데이터가 변경되어 하나의 컴포넌트에 속하는 UI에 영향을 미치는 상태
2. 컴포넌트 간 상태: 하나의 컴포넌트가 아니라 다수의 컴포넌트에 영향을 미치는 상태 (props drilling을 거치는)
3. 전역 상태: 애플리케이션의 모든 컴포넌트에 영향을 미치는 상태(사용자 인증 같은)
컴포넌트 간 상태나 전역 상태의 경우, 데이터를 넣고 전체 props 함수를 업데이트 하는 행위는 번거로울 수 있다.
그래서 React가 내부에서 지원하는 Context가 존재하고, 이를 사용해서 관리할 수 있었다.
그런데 이 Context도 단점이 있다.
Context의 단점
React의 Context에는 잠재적인 단점이 있다.
이 단점이 얼마나 영향을 미치는지에 따라 Redux를 비롯한 상태 관리 라이브러리를 사용할지 말지를 결정하면 된다.
1. 설정 및 상태 관리가 복잡해질 수 있다.
=> 작은 규모의 애플리케이션이라면 크게 문제가 되지 않지만, 그 이상의 규모라면 관리하기가 힘들어진다.
=> ContextProvider 컴포넌트가 다수 중첩된다든가(복잡함), 하나의 ContextProvider에 다 때려 박는다든가(개별 관리 힘듦)
2. Context는 데이터가 자주 변경되는 경우에는 적합하지 않다 (오피셜)
다크 모드와 같이 어떤 테마를 변경하거나 인증을 하는 식의 저빈도 업데이트에는 적합하다.
하지만 이런 예시가 아니라 유저가 무엇을 입력한다든지 모달이라든지 데이터가 자주 바뀌는 고빈도 업데이트에는 좋지 않다.
아마도 어떤 것이 변경되면 통째로 리렌더링이 일어나는 경우가 있기 때문에 고빈도 업데이트의 경우 적합하지 않다고 하는 것 같다.
결국 이 내용을 크게 보면 React에서 지원하는 Context VS 상태 관리 라이브러리(Redux 포함)의 구도라고 할 수 있다.
이제 후자를 선택하게 되면 Redux, Zustand, Recoil, Jotai 등 다른 상태 관리 라이브러리들끼리 비교해야 하는 것이고...
그래서 우선 Context에는 이러한 단점이 있으니 이게 싫다면 Redux를 한 번 맛봐볼래? 라는 것이다.
Redux의 작동 방식
1. Redux는 애플리케이션에 있는 하나의 중앙 상태 저장소(store)다.
이는 단 1개여야 하며, 이 곳에 모든 상태를 저장한다.
2. 컴포넌트에서 이 저장소를 구독(subscribe)한다.
컴포넌트의 데이터가 변경되면 저장소는 변경된 데이터를 컴포넌트에 알려준다.
컴포넌트는 변경된 데이터를 받고 이를 토대로 업데이트를 진행한다.
이 때 컴포넌트는 절대로 저장된 데이터를 직접 조작하지 않고, Reducer라는 함수를 사용한다.
3. 컴포넌트에서 어떤 액션을 발송(dispatch)하면 Reducer 함수로 보내진다.
그래서 컴포넌트 대신 Reducer가 저장소에 있는 데이터를 변경해준다.
이 때 '어떤 액션'은 타입 속성(type)을 가진다.
상태를 변경하기 위해 애플리케이션에서 일어나는 모든 변화를 나타내는 객체이기도 하다.
텍스트로만 보면 이해하기 어려울 수 있으니 코드로 알아보자!
먼저 npm init -y로 새 프로젝트를 초기화 하고 npm i redux로 Redux를 설치한다.
redux-demo라는 이름의 JS 파일을 만든 후 require로 Redux를 import한다. (예제는 nodeJS로 실행함)
const redux = require('redux');
const counterReducer = (state = { counter: 0 }, action) => {
return {
counter: state.counter + 1,
};
};
const store = redux.createStore(counterReducer);
const counterSubscriber = () => {
const latestState = store.getState();
};
store.subscribe(counterSubscriber);
console.log(store.getState()); // { counter: 1 }
store.dispatch({ type: 'plus' });
console.log(store.getState()); // // { counter: 2 }
일단 필요한 건 Reducer 함수, store, 구독할 컴포넌트, dispatch할 action 객체다.
1) Reducer 함수
Reducer 함수는 기존의 상태와 발송된 액션이라는 2가지 파라미터가 필요하다.
그리고 반환값으로는 새로운 상태의 '객체'를 리턴한다.
따라서 state, action을 파라미터로 전달하고 객체를 리턴하도록 코드를 작성한다.
2) store
createStore 메서드를 사용해 Redux 스토어를 생성한다.
counterReducer를 통해 상태가 변경되면 이를 반영하기 위해 createStore에 넣어준다.
(저장소는 데이터를 조작하는 Reducer 함수가 누군지 알아야 하기 때문)
3) 구독
counterSubscriber는 store에서 getState를 호출할 수 있다.
getState는 createStore()로 생성된 저장소에서 사용할 수 있는 메서드로, 업데이트 된 후 최신 상태의 스냅샷을 제공한다.
즉 업데이트 된 후의 값을 말하니 counter가 0 => 1이 된 { counter : 1 }을 제공하게 될 것이다.
그래서 콘솔에 찍은 첫 번째 코드는 { counter : 1 } 을 찍게 되는 것이다.
4) dispatch할 action 객체
dispatch()는 액션을 발송하는 메소드고, 액션은 JS 객체다. 이 객체의 문자열은 고유해야 한다.
현재 plus라는 액션을 발송해 Reducer가 실행되어 또 한 번 값이 증가했기 때문에 2번째 콘솔에는 { counter : 2 }가 되었다.
저 예제 코드에는 이상한 점이 있는데, 바로 Reducer 함수에 action이 정의되지 않았다는 것이다.
현재 정의되지 않았기 때문에 plus든 minus든 뭐라고 하든 state가 변경되게 된다.
어떤 액션에 따라 어떻게 상태를 업데이트 할지를 정해보자!
const redux = require('redux');
const counterReducer = (state = { counter: 0 }, action) => {
if (action.type === 'plus') {
return {
counter: state.counter + 1,
};
}
if (action.type === 'minus') {
return {
counter: state.counter - 1,
};
}
};
const store = redux.createStore(counterReducer);
const counterSubscriber = () => {
const latestState = store.getState();
};
store.subscribe(counterSubscriber);
store.dispatch({ type: 'plus' });
console.log(store.getState()); // { counter: 1 }
store.dispatch({ type: 'minus' });
console.log(store.getState()); // { counter: 0 }
action의 type을 plus, minus 2개로 나누었다.
plus를 dispatch하면 기존 state에서 +1이 되어 { counter: 1 }이 나오게 된다.
반대로 minus를 dispatch하면 현재 1인 값에서 -1이 되어 { counter: 0 }이 된다.
이런 식으로 Redux가 동작하는 원리를 알아보았다.
'⚛️ React > ✨ Redux' 카테고리의 다른 글
[Redux] Redux-toolkit 알아보기 (0) | 2024.04.14 |
---|---|
[Redux] Redux의 payload와 다중 state 작업하기 (0) | 2024.04.12 |
[Redux] useSelector, useDispatch로 상태 업데이트 하기 (0) | 2024.04.12 |