리덕스는 전역데이터를 어떻게 효과적으로 관리할 것인지에 대한 라이브러리로, 컴포넌트들의 상태 관련 로직들을 다른 파일들로 분리시켜서 더욱 효율적으로 관리할 수 있으며 글로벌 상태 관리도 손쉽게 할 수 있다.
🧐 Context 와 다른점은 뭘까?
리덕스로 상태 관리를 할 때 리듀서 함수를 사용한다. 리덕스의 미들웨어를 사용하면 액션 객체가 리듀서에서 처리되기 전에 우리가 원하는 작업들을 수행할 수 있다.
connect
함수를 사용해서 리덕스의 상태 또는 액션 생성 함수를 컴포넌트의 props로 받아올 수 있으며 useSelector
, useDispatch
, useStore
와 같은 Hooks 를 사용하면 손쉽게 상태를 조회하거나 액션을 디스패치 할 수 있다.
리덕스에서는 모든 글로벌 상태를 하나의 커다란 상태 객체에 넣어서 사용하는 것이 필수이다. (매번 Context를 새로 만드는 수고로움을 덜 수 있다.)
✍️ 단순히 글로벌 상태 관리를 위한 것이라면 Context API를 활용하는 것만으로도 충분할 수 있음에 유의하자.
하나의 애플리케이션에서는 단 한개의 스토어를 만들어서 사용한다.
1) import redux
2) 액션 정의
3) 액션을 사용하는 리듀서를 만들기
4) 리듀서들을 합치기
5) 합쳐진 최종 리듀서를 인자로, 단일 스토어를 만들기
1) import react-redux
2) connect 함수를 이용해서 컴포넌트에 연결하기
상태는 읽기전용이다. 리액트에서 상태를 업데이트 할 때와 마찬가지로 기존의 상태는 건들이지 않고 새로운 상태를 생성하여 업데이트 해준다.
동일한 인풋이라면 언제나 동일한 아웃풋이 있어야 한다.
+) 순수하지 않은 작업들은 리듀서 함수의 바깥에서 처리해줘야 하는데 이런 것들을 위해 리덕스 미들웨어를 사용한다.
1) Type 프로퍼티만 가지고있는 액션
{type: 'TEST'}
→ payload가 없는 액션
2) 다른 프로퍼티가 있는 액션
{type: 'TEST', params:'hi'}
→ payload가 있는 액션
{
type: "TEST", // type 필드는 필수적이다.
data: {
id: 0,
text: "리덕스 연습!"
}
}
function 액션생성자(...args) {return 액션;}
export
키워드를 붙여서 불러와 사용한다.// 타입
const ADD_TODO = 'ADD_TODO';
// ADD_TODO 타입을 이용하는 액션생성함수
export function addTodo(todo) {
return {
type: ADD_TODO,
todo,
};
}
// 화살표 함수로도 만들 수 있다.
export const addTodo = (todo) => ({
type: ADD_TODO,
todo
})
function 리듀서(previousState, action){
// 상태 업데이트 로직
return newState
}
ex.1 )
import { ADD_TODO } from './actions';
const initialState = [];
function todoApp(previousState = initialState, action) {
if (action.type === ADD_TODO) {
return [...previousState, action.todo]; // 1)
}
return previousState;
}
1) Immutable 하게 데이터를 업데이트 하기
❗️만약 previousState.push('');
와 같이 작성한다면 객체는 변경하지만 객체 레퍼런스는 변경되지 않으므로 리덕스에서 이부분을 바뀌었다고 판단하지 못한다. (Immutable하지 않은 코드)
ex.2 )
// 카운터를 위한 리듀서 예시
function counter(state, action) {
switch (action.type) {
case 'INCREASE':
return state + 1;
case 'DECREASE':
return state - 1;
default: // 1)
return state;
}
}
1) 리덕스의 리듀서에는 default 값으로 기존 State를 반환하도록 작성한다.
const store = createStore(reducer)
console.log(store);
console.log(store.getState()); // 현재 state 상태 출력
store.dispatch(addTodo('coding'));
console.log(store.getState()); // 현재 state 상태 출력
unsubscribe()
하면 제거된다.store.subscribe(() => {
console.log(store.getState()); // 2,3,4)
});
console.log(store.getState()); // 1)
store.dispatch(addTodo('coding'));
store.dispatch(addTodo('sleep'));
store.dispatch(addTodo('eat'));
console.log(store.getState()); // 5)
// 1) []
// 2) ['coding']
// 3) ['coding', 'sleep']
// 4) ['coding', 'sleep', 'eat']
// 5) ['coding', 'sleep', 'eat']
✍️ 보통 리액트에서 리덕스를 사용할 때에는 subscribe
내장함수 대신 react-redux 라이브러리에서 제공하는 connect
함수 또는 useSelector
Hook을 사용해서 리덕스 스토어의 상태에 구독을 한다.
CombineReducers
함수를 사용한다.▼ combineReducers 활용 전 코드
import { ADD_TODO, COMPLETE_TODO, SHOW_ALL, SHOW_COMPLETE } from './actions';
const initialState = { todos: [], filter: 'ALL' };
export function todoApp(previousState = initialState, action) {
if (action.type === ADD_TODO) { // 1)
return {
...previousState,
todos: [...previousState.todos, { text: action.text, done: false }],
};
}
if (action.type === COMPLETE_TODO) { // 2)
return {
...previousState,
todos: previousState.todos.map((todo, index) => {
if (index === action.index) {
return { ...todo, done: true };
}
return todo;
}),
};
}
if (action.type === SHOW_COMPLETE) { // 3)
return {
...previousState,
filter: 'COMPLETE',
};
}
if (action.type === SHOW_ALL) { // 4)
return {
...previousState,
filter: 'ALL',
};
}
return previousState;
}
📌 reducers 폴더에 아래와 같은 폴더 구조를 작성
-reducer.js
-filter.js
-todos.js
▼ combineReducers 활용 & 폴더 분리
// reducer.js
import { combineReducers } from 'redux';
import todos from './todos';
import filter from './filter';
const reducer = combineReducers({ // 이 부분을 reducer 대신 rootReducer라고 쓰는 듯 하다.🧐
todos,
filter,
});
export default reducer;
// filter.js
import { SHOW_ALL, SHOW_COMPLETE } from '../actions';
const initialState = 'ALL';
export default function filter(previousState = initialState, action) {
if (action.type === SHOW_COMPLETE) {
return 'COMPLETE';
}
if (action.type === SHOW_ALL) {
return 'ALL';
}
return previousState;
}
// todos.js
import { ADD_TODO, COMPLETE_TODO } from '../actions';
const initialState = [];
export default function todos(previousState = initialState, action) {
if (action.type === ADD_TODO) {
return [...previousState, { text: action.text, done: false }];
}
if (action.type === COMPLETE_TODO) {
return previousState.map((todo, index) => {
if (index === action.index) {
return { ...todo, done: true };
}
return todo;
});
}
return previousState;
}
reference)
redux-공식문서
pc-redux
vlpt-redux