리덕스란?
리덕스는 가장 많이 사용하는 리액트 상태 관리 라이브러리이다. 리덕스를 이용하면 컴포넌트의 상태 업데이트 관련 로직을 다른 파일로 분리시켜서 더욱 효율적으로 관리할 수 있다.
Redux 시작하기 | Redux
소개 > 시작하기: Redux를 배우고 사용하기 위한 자료
ko.redux.js.org
리덕스는 전역 상태 관리를 지원하며, 단방향 데이터 흐름을 따른다. 이는 애플리케이션에서 상태를 변경할 때, 컴포넌트에서 발생한 이벤트가 아닌, 액션(Action)을 발생시켜 상태를 변경한다. 액션은 일종의 객체이며, 해당 객체는 상태를 변경하는 데 필요한 정보를 가지고 있다. 예를 들어, 사용자가 로그인하는 경우, 로그인 액션은 사용자 정보를 가진 객체를 생성한다.
액션을 생성한 후, 이를 리듀서(Reducer)에 전달한다. 리듀서는 이전 상태와 액션을 받아 새로운 상태를 반환하는 함수이다. 이 과정을 통해 상태를 변경할 수 있다.
리덕스의 핵심 개념은 "단 하나의 스토어" 이다. 모든 상태는 하나의 스토어에 저장되며, 스토어의 상태는 불변성을 유지한다. 이는 상태를 직접 수정하는 것이 불가능하며, 새로운 상태를 반환하는 방식으로 상태를 변경한다.
리덕스는 또한 미들웨어(Middleware)를 지원한다. 미들웨어는 액션과 리듀서 사이에서 동작하는 함수이며, 미들웨어를 사용하면 액션을 처리하기 전에 특정 작업을 수행할 수 있다. 예를 들어, 비동기 작업을 수행하는 미들웨어를 사용하여 API 호출을 처리할 수 있다.
리덕스의 주요 이점 중 하나는 개발자 도구이다. 리덕스 개발자 도구를 사용하면 애플리케이션의 상태 변경과 액션 발생을 모니터링할 수 있으며, 디버깅에 도움이 된다.
총론적으로, 리덕스는 React와 함께 사용되어 상태 관리를 효과적으로 처리할 수 있는 라이브러리이며, 단방향 데이터 흐름과 불변성을 유지하는 상태 관리 패턴을 따른다.
개념 미리 정리하기
액션(Action)
리덕스는 상태 관리 라이브러리로, 애플리케이션의 상태를 하나의 스토어에서 관리하고 이를 변경하기 위해 액션을 발생시킨다. 액션은 일종의 객체로, 반드시 type 속성을 가지며 다른 임의의 속성들을 가질 수 있다. 액션을 발생시키면 리듀서(reducer)가 이를 처리하여 상태를 변경하게 된다.
타입스크립트에서 사용하는 리덕스 액션의 예시이다.
// 액션 타입 정의
enum ActionTypes {
INCREMENT = "INCREMENT",
DECREMENT = "DECREMENT",
}
// 액션 생성자 함수 정의
interface IncrementAction {
type: ActionTypes.INCREMENT;
payload: {
value: number;
};
}
interface DecrementAction {
type: ActionTypes.DECREMENT;
payload: {
value: number;
};
}
type CounterAction = IncrementAction | DecrementAction;
function increment(value: number): IncrementAction {
return {
type: ActionTypes.INCREMENT,
payload: {
value,
},
};
}
function decrement(value: number): DecrementAction {
return {
type: ActionTypes.DECREMENT,
payload: {
value,
},
};
}
위 코드에서는 ActionTypes라는 열거형(enum)을 정의하여 INCREMENT와 DECREMENT 두 가지 타입의 액션을 구분하였다. 이후 IncrementAction과 DecrementAction이라는 두 가지 인터페이스를 정의하여 각각의 액션에 대한 속성을 명시했다. 마지막으로 CounterAction이라는 유니온 타입을 정의하여 액션 타입을 하나로 묶어줬다.
increment와 decrement 함수는 각각 value라는 매개변수를 받아 해당 값을 페이로드(payload)에 담아 액션 객체를 생성하고 반환한다.
액션 생성 함수
액션 객체를 생성하기 위해서는 일반적으로 액션 생성자 함수를 사용한다. 액션 생성자 함수는 액션 객체를 반환하는 순수 함수이며, 다음과 같은 형식을 가진다.
function increment(value: number): IncrementAction {
return {
type: ActionTypes.INCREMENT,
payload: {
value,
},
};
}
function decrement(value: number): DecrementAction {
return {
type: ActionTypes.DECREMENT,
payload: {
value,
},
};
}
위 코드에서, increment와 decrement가 액션 생성 함수이다. 이 함수를 통해 액션 객체가 만들어진다. 예를 들어, increment 함수는 value라는 숫자 값을 인자로 받아 ActionTypes.INCREMENT 타입의 액션 객체를 반환한다. 이 때 액션 객체는 다음과 같은 형태로 구성된다.
{
type: ActionTypes.INCREMENT,
payload: {
value: value
}
}
decrement 함수 역시 마찬가지로 액션객체를 반환해준다. 여기서 type
은 액션의 타입을 나타내며, payload
는 액션에 필요한 추가 데이터를 담고 있다. 이렇게 생성된 액션 객체는 액션 객체의 타입과 함께 dispatch
함수에 전달되어 리듀서 함수에서 처리된다.
액션 생성자 함수는 액션 객체를 생성하는 과정을 추상화하여 코드의 가독성과 유지보수성을 높인다. 액션 생성자 함수를 사용하면 액션 객체를 생성하는 코드를 간단하게 작성할 수 있고, 동일한 액션을 여러 곳에서 사용해야 할 때 중복되는 코드를 제거할 수 있다. 또한, 타입스크립트와 같은 정적 타입의 검사를 사용해 액션 객체의 타입을 보장할 수 있다.
리듀서(reducer)
위에서 정의된 액션과 액션 생성 함수를 사용해 리듀서를 작성할 수 있다. 리듀서 함수는 액션 객체의 타입에 따라 새로운 상태 객체를 반환하는 함수이다. 이에 대한 예시는 아래와 같다.
interface CounterState {
value: number;
}
const initialState: CounterState = {
value: 0,
};
function counterReducer(
state: CounterState = initialState,
action: CounterAction
): CounterState {
switch (action.type) {
case ActionTypes.INCREMENT:
return {
value: state.value + action.payload.value,
};
case ActionTypes.DECREMENT:
return {
value: state.value - action.payload.value,
};
default:
return state;
}
}
위 코드에서는 CounterState라는 인터페이스를 정의해 상태의 타입을 명시했다. initialState는 초기 상태를 나타내는 객체이며, counterReducer 함수에서는 state와 action을 매개변수로 받아 switch 문을 통해 액션 타입에 따라 상태를 변경하고 반환한다. 여기서 increament 액션을 발생시켜보자.
const incrementAction = increment(10);
// { type: 'INCREMENT', payload: { value: 10 } }
const decrementAction = decrement(5);
// { type: 'DECREMENT', payload: { value: 5 } }
이렇게 생성된 액션 객체는 리덕스의 dispatch 함수를 사용해 스토어에 전달할 수 있다.
store.dispatch(incrementAction);
store.dispatch(decrementAction);
이제 counterReducer 함수가 이러한 액션을 처리해 상태를 변경한다.
console.log(store.getState());
// { value: 5 }
store.dispatch(incrementAction);
console.log(store.getState());
// { value: 15 }
store.dispatch(decrementAction);
console.log(store.getState());
// { value: 10 }
액션은 애플리케이션 상태를 변경하기 위해 사용되는 중요한 개념이며, 단순한 객체이기 때문에 다양한 방법으로 액션을 생성할 수 있다. 예시 코드에서는 액션 생성 함수를 통해 액션을 생성하였지만, 필요에 따라 직접 객체를 생성해 액션을 발생시키는 것도 가능하다. 리듀서는 이러한 액션에 따라 상태를 변경하므로, 액션을 올바르게 정의하고 발생하는 것은 상태 관리에 있어 매우 중요하다.
스토어(store)
리덕스에서 스토어는 애플리케이션의 상태를 저장하고, 액션을 디스패치해서 상태를 업데이트하고, 상태 변화를 구독할 수 있는 객체이다.
스토어 객체는 createStore 함수를 통해 생성된다. 이 함수는 첫 번째 인자로 리듀서 함수를 받고, 두 번째 인자로 초기 상태 값을 받는다. 예를 들어, 다음과 같이 스토어 객체를 생성할 수 있다.
import { createStore } from 'redux';
interface CounterState {
value: number;
}
const initialState: CounterState = {
value: 0,
};
function counterReducer(state: CounterState = initialState, action: any) {
switch (action.type) {
case 'INCREMENT':
return { value: state.value + 1 };
case 'DECREMENT':
return { value: state.value - 1 };
default:
return state;
}
}
const store = createStore(counterReducer, initialState);
위 코드에서 createStore
함수는 counterReducer
함수와 initialState
객체를 이용해 스토어 객체를 생성한다. 이 스토어 객체는 dispatch, getState, subscribe, replaceReducer
등의 메서드를 가지고 있다.
디스패치(dispatch)
디스패치는 스토어의 내장 함수 중 하나이다. 디스패치는 '액션을 발생시키는 것'이라고 이해하면 된다. 이 함수는 dispatch(action)과 같은 형태로 액션 객체를 파라미터로 넣어서 호출한다.
일반적으로 리액트 애플리케이션에서는 useDispatch 훅을 사용해 스토어의 디스패치 메서드에 접근할 수 있다. useDispatch 훅은 리덕스 스토어에서 dispatch 메서드를 반환한다.
import { useDispatch } from 'react-redux';
import { increment, decrement } from './actions';
function Counter() {
const dispatch = useDispatch();
const handleIncrement = () => {
dispatch(increment(1));
};
const handleDecrement = () => {
dispatch(decrement(1));
};
return (
<div>
<h1>Counter</h1>
<p>{/* 상태 값 출력 */}</p>
<button onClick={handleIncrement}>+</button>
<button onClick={handleDecrement}>-</button>
</div>
);
}
위 코드는 useDispatch 훅을 사용해 스토어의 dispatch 메서드를 가져온다. handleIncrement 함수와 handleDecrement 함수에서는 increment 액션과 decrement 액션을 디스패치 하고 있다. 디스패치를 통해 스토어는 리듀서 함수를 실행시켜서 새로운 상태를 만들어준다.
구독(subscribe)
구독도 스토어의 내장 함수 중 하나이다. 구독 함수는 스토어에서 상태가 변경될 때마다 호출되는 콜백함수를 등록하는 메서드이다. 구독함수를 사용하면 상태가 변경될 때마다 UI를 업데이트하거나 다른 작업을 수행할 수 있다.
리덕스에서는 store.subscribe
를 통해 구독을 등록할 수 있다. 이 메서드는 콜백 함수를 인자로 받는다. 콜백 함수는 상태가 변경될 때마다 호출된다. 콜백 함수는 현재 스토어 상태를 인자로 받을 수 있으며, 일반적으로 이를 통해 UI를 업데이트한다.
다음은 구독 함수를 사용해 상태가 변경될 때마다 콘솔에 로그를 출력하는 예시 코드이다.
import { createStore } from 'redux';
function counterReducer(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
const store = createStore(counterReducer);
store.subscribe(() => {
console.log(store.getState());
});
store.dispatch({ type: 'INCREMENT' }); // 콘솔에 "1" 출력
store.dispatch({ type: 'INCREMENT' }); // 콘솔에 "2" 출력
store.dispatch({ type: 'DECREMENT' }); // 콘솔에 "1" 출력
위 예시 코드에서는 store.subscribe
메서드를 사용해 콘솔에 현재 스토어 상태를 출력하는 콜백 함수를 등록하고 있다. store.dispatch
메서드를 사용해 액션을 디스패치하고, 상태가 변경될 때마다 등록된 콜백 함수가 호출되어 콘솔에 로그를 출력한다.
리덕스에서 구독 함수는 주로 UI 라이브러리(React, Vue.js 등)와 함께 사용되어 UI를 업데이트하는데 사용된다. UI 라이브러리와 함께 사용할 때는 보통 useEffect 훅과 같은 라이브러리에서 제공하는 라이프사이클 메서드를 사용하여 구독 함수를 등록하고, 등록된 콜백 함수에서 UI를 업데이트한다.
리덕스 사용하기
리덕스는 리액트에 종속되는 라이브러리가 아니기 때문에, 다른 UI 라이브러리 및 프레임워크와 함께 사용할 수도 있다. 이번에는 순수 타입스크립트 환경에서 리덕스를 사용해보도록 하자.
Parcel로 프로젝트 생성
Parcel은 웹 애플리케이션 번들러(Bundler)이다. 번들러는 여러개의 모듈과 파일들을 하나의 번들(bundle) 파일로 만들어주는 도구이며, 이를 통해 여러개의 파일들을 하나의 파일로 합치고 최적화하여 웹 애플리케이션의 로딩 속도를 높일 수 있다.
Parcel은 자동으로 필요한 의존성(dependency)을 분석하고, 이를 기반으로 번들링을 수행한다. 이는 별도의 설정이 필요하지 않으므로 프로젝트를 빠르게 시작할 수 있게 해준다. 또한, 모듈 번들링 외에도 HTML, CSS, 이미지 등의 파일도 자동으로 번들링할 수 있다.
간단한 명령어로 웹 애플리케이션 번들링을 수행할 수 있으며, 개발용 서버와 빌드를 지원한다.
Parcel의 장점은 다음과 같다.
- 설정 없이 간편하게 사용할 수 있다.
- 다양한 프로젝트 타입을 지원한다.
- 자동으로 의존성을 분석하고 번들링한다.
- 기본적으로 필요한 플러그인이 내장되어 있다.
- 개발용 서버와 빌드를 지원한다.
단점으로는 다른 번들러와 비교했을 때 세밀한 커스터마이징이 어렵다는 점이 있다.
yarn global add @types/parcel-bundler
// yarn global add parcel-bundler
// npm install -g parcel-bundler
mkdir vanillar-redux
cd vanillar-redux
yarn init -y
// package.json
{
"name": "vanillar-redux",
"version": "1.0.0",
"main": "index.tsx",
"license": "MIT",
"devDependencies": {
"typescript": "^5.0.2"
}
}
// index.html
<html>
<body>
<div>Vanillar typescript</div>
<script src="./index.tsx"></script>
</body>
</html>
// index.tsx
console.log('hello world');
다 작성한 후, 다음 명령어를 실행해보면 개발용 서버가 실행된다.
parcel index.html
터미널에 표시된 개발 서버의 주소로 들어가면, 다음과 같은 페이지와 콘솔을 확인해볼 수 있다.
간단한 UI 구성
먼저 리덕스 모듈을 설치하자.
yarn add redux
그리고, css 파일을 작성한다.
/* index.css */
.toggle {
border: 2px solid black;
width: 64px;
height: 64px;
border-radius: 32px;
box-sizing: border-box;
}
.toggle.active {
background: yellow;
}
// index.html
<html>
<head>
<link rel="stylesheet" type="text/css" href="index.css" />
</head>
<body>
<div class="toggle"></div>
<hr />
<h1>0</h1>
<button id="increase">+1</button>
<button id="decrease">-1</button>
<script src="./index.tsx"></script>
</body>
</html>
DOM 레퍼런스 생성
이 프로젝트는 UI를 관리할 때 별도의 라이브러리를 사용하지 않기 때문에 DOM을 직접 수정해주어야 한다. 다음과 같이 파일 상단에 수정할 DOM 노드를 가리키는 값을 미리 선언해준다.
// index.tsx
const divToggle = document.querySelector(".toggle") as HTMLDivElement;
const counter = document.querySelector("h1") as HTMLHeadingElement;
const buttonIncrease = document.querySelector("#increase") as HTMLButtonElement;
const buttonDecrease = document.querySelector("#decrease") as HTMLButtonElement;
이 코드는 HTML 요소를 선택하고 각각의 타입스크립트 유형으로 형변환해준다.
액션 타입과 액션 생성 함수 정의
프로젝트의 상태에 변화를 일으키는 것을 액션이라고 한다. 먼저 액션에 이름을 정의해준다. 액션 이름은 문자열 형태로, 주로 대문자로 작성하며 액션 이름은 고유해야 한다. 이름이 중복되면 의도하지 않은 결과가 발생할 수 있기 때문이다.
index.tsx 파일에 다음 코드를 추가하자.
const TOGGLE_SWITCH = "TOGGLE_SWITCH" as const;
const INCREASE = "INCREASE" as const;
const DECREASE = "DECREASE" as const;
이 부분은 리덕스 액션에서 사용되는 문자열 상수를 만든다. as const
를 사용해 타입스크립트에게 문자열 상수임을 알려주며, 이는 문자열 리터럴 유니온 타입으로 사용될 수 있다.
다음으로 액션 이름을 사용해 액션 객체를 만드는 액션 생성 함수를 작성해준다. 액션 객체는 type 값을 반드시 가지고 있어야 하며, 그 외에 추후 상태를 업데이트할 때 참고하고 싶은 값은 마음대로 넣을 수 있다.
index.tsx 파일에 다음 코드를 추가하자.
type ToggleSwitchAction = {
type: typeof TOGGLE_SWITCH;
};
type IncreaseAction = {
type: typeof INCREASE;
difference: number;
};
type DecreaseAction = {
type: typeof DECREASE;
};
type CounterAction = ToggleSwitchAction | IncreaseAction | DecreaseAction;
const toggleSwitch = (): ToggleSwitchAction => ({ type: TOGGLE_SWITCH });
const increase = (difference: number): IncreaseAction => ({
type: INCREASE,
difference,
});
const decrease = (): DecreaseAction => ({ type: DECREASE });
위 코드는 리덕스 액션의 유형을 정의한다. 각각의 액션 유형은 type
필드와 함께 객체로 표현된다. 그리고 이 세 타입을 대표하는 CounterAction 타입을 정의했다.
또한, 리덕스 액션 생성 함수도 정의해주었다. toggleSwitch
함수는 ToggleSwitchAction
객체를 반환하며, increase
함수는 IncreaseAction
객체를 반환합니다. decrease
함수는 DecreaseAction
객체를 반환합니다.
초깃값 설정
이 프로젝트에서 사용할 초깃값을 정의해준다. 초깃값은 자유롭게 설정 가능하다.
index.tsx에 추가해준다.
type RootState = {
toggle: boolean;
counter: number;
};
const initialState: RootState = {
toggle: false,
counter: 0,
};
상태는 toggle과 counter 필드를 가진 객체이다.
리듀서 함수 정의
리듀서는 변화를 일으키는 함수이다. 함수의 파라미터는 state와 action 값을 받아온다.
index.tsx에 추가해준다.
function reducer(
state: RootState = initialState,
action: CounterAction
): RootState {
switch (action.type) {
case TOGGLE_SWITCH:
return {
...state,
toggle: !state.toggle,
};
case INCREASE:
return {
...state,
counter: state.counter + action.difference,
};
case DECREASE:
return {
...state,
counter: state.counter - 1,
};
default:
return state;
}
}
위의 reducer 함수는 상태를 관리하는 함수로, 이전 상태와 액션을 받아서 새로운 상태를 반환한다. toggleSwitch, increase, decrease 함수는 각각 ToggleSwitchAction, IncreaseAction, DecreaseAction 타입의 액션 객체를 생성한다. toggleSwitch 함수는 TOGGLE_SWITCH 타입의 액션 객체를 생성하며, increase 함수는 INCREASE 타입의 액션 객체와 함께 difference 값을 받는다. decrease 함수는 DECREASE 타입의 액션 객체를 생성한다.
스토어 생성
스토어는 createStore를 통해 만들 수 있다.
index.tsx에 다음 코드를 추가하자.
import { legacy_createStore as createStore } from "redux";
(...)
const store = createStore(reducer);
리덕스의 버전에 따라 createStore가 취소선이 그어져 있을수도 있다(vscode 환경에서). 이럴 경우, legacy_createStore로 대체해도 무방하다.
createStore 함수는 리덕스에서 상태 관리를 위한 중심점인 스토어를 생성하는 함수이다. 이 함수는 reducer 함수와 초기 상태 값을 인자로 받아 스토어 객체를 반환한다.
render 함수 생성
상태가 업데이트될 때마다 호출되며, 리액트의 render 함수와는 다르게 이미 html을 사용해 만들어진 UI의 속성을 상태에 따라 변경해준다.
index.tsx에 코드를 추가하자.
function render(): void {
const state = store.getState();
if (state.toggle) {
divToggle.classList.add("active");
} else {
divToggle.classList.remove("active");
}
counter.innerText = state.counter.toString();
}
render();
구독하기
이제 스토어의 상태가 바뀔 때마다 방금 만든 render 함수가 호출이 되도록 해 줄 것이다. 이 작업은 스토어의 내장 함수 subscribe를 사용해 수행할 수 있다.
index.tsx에 코드를 한줄 추가해주자.
store.subscribe(render);
액션 발생시키기
액션을 발생시키는 것을 디스패치라고 한다. 디스패치를 할 때는 스토어의 내장 함수 dispatch를 사용한다. 파라미터는 액션 객체를 넣어주면 된다.
다음과 같이 DOM 요소에 클릭 이벤트를 설정하자. 이벤트 내부 함수에서는 dispatch 함수를 사용해 액션을 스토어에 전달해줄 수 있다.
divToggle.onclick = () => {
store.dispatch(toggleSwitch());
};
buttonIncrease.onclick = () => {
store.dispatch(increase(1));
};
buttonDecrease.onclick = () => {
store.dispatch(decrease());
};
마지막으로 index.tsx에 코드를 추가해주었다. 여기까지 코드를 작성해주었을 때, 브라우저에 렌더링해서 상태 변화를 확인하자.
정리
index.tsx의 전체 코드는 다음과 같다.
import { legacy_createStore as createStore } from "redux";
// DOM 요소들
const divToggle = document.querySelector(".toggle") as HTMLDivElement;
const counter = document.querySelector("h1") as HTMLHeadingElement;
const buttonIncrease = document.querySelector("#increase") as HTMLButtonElement;
const buttonDecrease = document.querySelector("#decrease") as HTMLButtonElement;
// 액션 타입
const TOGGLE_SWITCH = "TOGGLE_SWITCH" as const;
const INCREASE = "INCREASE" as const;
const DECREASE = "DECREASE" as const;
// 액션 생성 함수들
type ToggleSwitchAction = {
type: typeof TOGGLE_SWITCH;
};
type IncreaseAction = {
type: typeof INCREASE;
difference: number;
};
type DecreaseAction = {
type: typeof DECREASE;
};
type CounterAction = ToggleSwitchAction | IncreaseAction | DecreaseAction;
const toggleSwitch = (): ToggleSwitchAction => ({ type: TOGGLE_SWITCH });
const increase = (difference: number): IncreaseAction => ({
type: INCREASE,
difference,
});
const decrease = (): DecreaseAction => ({ type: DECREASE });
// 상태 타입과 초기 상태
type RootState = {
toggle: boolean;
counter: number;
};
const initialState: RootState = {
toggle: false,
counter: 0,
};
// 리듀서 함수
function reducer(
state: RootState = initialState,
action: CounterAction
): RootState {
switch (action.type) {
case TOGGLE_SWITCH:
return {
...state,
toggle: !state.toggle,
};
case INCREASE:
return {
...state,
counter: state.counter + action.difference,
};
case DECREASE:
return {
...state,
counter: state.counter - 1,
};
default:
return state;
}
}
// 스토어 생성
const store = createStore(reducer);
// 렌더링 함수
function render(): void {
const state = store.getState();
if (state.toggle) {
divToggle.classList.add("active");
} else {
divToggle.classList.remove("active");
}
counter.innerText = state.counter.toString();
}
// 초기 렌더링
render();
// 스토어 구독
store.subscribe(render);
// 이벤트 처리 함수들
divToggle.onclick = () => {
store.dispatch(toggleSwitch());
};
buttonIncrease.onclick = () => {
store.dispatch(increase(1));
};
buttonDecrease.onclick = () => {
store.dispatch(decrease());
};
위 코드는 정리하자면 리덕스 애플리케이션이 된다. HTML에서 사용되는 요소들과 Redux 관련 코드들이 모두 하나의 파일에 작성되어 있으며, 스토어를 생성하고, 렌더링 함수를 호출한 뒤, 스토어를 구독한다. 스토어 구독은 스토어의 상태가 변경될 때마다 렌더링 함수를 호출하는 역할을 수행한다. 마지막으로, 사용자 이벤트가 발생하면 액션을 디스패치하도록 처리되어 있다.
리덕스의 규칙
리덕스는 세 가지의 규칙을 가지고 있다.
- 단일 스토어
- 어플리케이션의 상태는 모두 하나의 스토어 안에 객체 트리 형태로 저장된다.
- 다시 말해, 애플리케이션의 모든 상태는 하나의 객체로 관리된다.
- 리덕스에서 여러 개의 스토어를 관리하지 못하는 것은 아니다. 특정 업데이트가 너무 빈번하게 일어나거나 애플리케이션의 특정 부분을 완전히 분리시킬 때 여러 개의 스토어를 만들 수도 있지만, 상태 관리가 복잡할 수 있으므로 권장하지는 않는 방법이다.
- 읽기 전용 상태
- 액션을 통해서만 상태를 변경할 수 있다.
- 즉, 상태를 직접 수정하는 것이 불가능하다.
리덕스의 상태는 읽기 전용이다. 상태를 업데이트 할 때는 기존의 객체를 건드리지 않고 새로운 객체를 생성해주어야 한다.
리덕스에서 불변성을 유지하는 이유는 내부적으로 데이터가 변경되는 것을 감지하기 위해 얕은 비교 검사를 하기 때문이다. 객체의 변화를 감지할 때 객체의 깊은 곳까지 비교하는 것이 아닌 겉핥기 식으로 비교해 좋은 성능을 유지할 수 있는 것이다.
- 변화는 순수한 함수로 만들어진다.
- 상태의 변화를 일으키는 리듀서는 순수 함수로 작성되어야 한다.
- 리듀서는 이전 상태와 액션 객체를 받아서 새로운 상태 객체를 반환하는 함수이다.
- 순수 함수란 함수 내부에서 자신의 입력값을 변경하지 않으며, 동일한 인자값에 대해서는 항상 같은 값을 반환하는 함수를 의미한다.
예를 들어 리듀서 함수 내부에서 랜덤 값을 만들거나 네트워크 요청을 한다면, 파라미터가 같아도 다른 결과를 만들어낼 수 있기 때문에 사용하면 안된다. 이런 작업은 리듀서 함수 바깥에서 처리해주어야 하며, 리덕스 미들웨어를 통해 관리할 수도 있다.
'WEB' 카테고리의 다른 글
자바스크립트 비동기 작업 이해하기 [javascript/typescript] (0) | 2023.03.20 |
---|---|
타입스크립트 핸드북 훑어보기 [2. Everyday Types] (0) | 2023.03.11 |
타입스크립트 핸드북 훑어보기 [1. The Basics] (0) | 2023.03.10 |
타입스크립트 문서 훑어보기 (0) | 2023.03.09 |
[HTTP - 02] HTTP 트랜잭션 / TCP (0) | 2023.02.23 |