도서 리액트를 다루는 기술 | 김민준 을 읽고 작성한 게시글입니다.
리덕스를 사용하는 이유
- 소규모 프로젝트에서는 컴포넌트가 가진 state를 사용하는 것만으로도 충분하지만, 프로젝트의 규모가 커짐에 따라 상태 관리가 번거로워질 수 있다.
- 리덕스를 사용하면, 상태 업데이트에 관한 로직을 모듈로 따로 분리하여 컴포넌트 파일과 별개로 관리할 수 있으므로 코드를 유지 보수하는 데 도움이 된다.
- 또한, 여러 컴포넌트에서 동일한 상태를 공유해야 할 때 매우 유용하며, 실제 업데이트가 필요한 컴포넌트만 리렌더링되도록 쉽게 최적화해 줄 수도 있다.
react-redux 설치
yarn add redux react-redux
리덕스 사용하기
UI 준비하기
- 리액트 프로젝트에서 리덕스를 사용할 때 가장 많이 사용하는 패턴은 프레젠테이셔널 컴포넌트와 컨테이너 컴포넌트를 분리하는 것이다.
- 프레젠테이셔널 컴포넌트란 주로 상태 관리가 이루어지지 않고, 그저 props를 받아와서 화면에 UI를 보여 주기만 하는 컴포넌트를 말한다.
- 컨테이너 컴포넌트는 리덕스와 연동되어 있는 컴포넌트로, 리덕스로부터 상태를 받아오기도 하고, 리덕스 스토어에 액션을 디스패치하기도 한다.
- 이러한 패턴은 리덕스를 사용하는 데 필수 사항은 아니다.
- 다만, 이 패턴을 사용하면 코드의 재사용성도 높아지고, 관심사의 분리가 이루어져 UI를 작성할 때 좀 더 집중할 수 있다.
카운터 컴포넌트 UI 만들기
// src/components/Counter.tsx
const Counter: React.FC<{
count: number;
onIncrease?: () => void;
onDecrease?: () => void;
}> = ({ count, onIncrease, onDecrease }) => {
return (
<div>
<div>
<h1>{count}</h1>
<button onClick={onIncrease}>+1</button>
<button onClick={onDecrease}>-1</button>
</div>
</div>
);
};
export default Counter;
할 일 목록 컴포넌트 UI 만들기
- TodoItem 컴포넌트를 따로 분리한다.
// src/components/TodoItem.tsx
import Todo from "../types/Todo";
import "./TodoItem.css";
const TodoItem: React.FC<{
todo: Todo;
onToggle: (id: number) => void;
onRemove: (id: number) => void;
}> = ({ todo, onToggle, onRemove }) => {
return (
<div className={`TodoItem ${todo.done ? "done" : ""}`}>
<input type="checkbox" onChange={() => onToggle(todo.id)} />
<span className="text">{todo.text}</span>
<button className="remove" onClick={() => onRemove(todo.id)}>
X
</button>
</div>
);
};
export default TodoItem;
/* src/TodoItem.css */
.TodoItem.done .text {
text-decoration: line-through;
color: gray;
}
.TodoItem .remove {
color: red;
border: none;
background: none;
}
```ts
// src/components/Todos.tsx
import Todo from "../types/Todo";
import TodoItem from "./TodoItem";
const Todos: React.FC<{
todos: Todo[];
onInsert: (text: string) => void;
onToggle: (id: number) => void;
onRemove: (id: number) => void;
}> = ({ todos, onInsert, onToggle, onRemove }) => {
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
};
return (
<div>
<form onSubmit={onSubmit}>
<input />
<button type="submit">등록</button>
</form>
<div>
<TodoItem todo="할 일" />
<TodoItem todo="할 일" />
<TodoItem todo="할 일" />
</div>
</div>
);
};
export default Todos;
function App() {
return (
<div className="App">
<Counter count={0} />
<hr />
<Todos />
</div>
);
}
리덕스 관련 코드 작성하기
- 리덕스를 사용할 땐 액션 타입, 액션 생성 함수, 리듀서 코드를 작성해야 한다.
- 이 코드들을 각각 다른 파일에 작성하는 방법도 있고, 기능별로 묶어서 파일 하나에 적성하는 방법도 있다.
- 왼쪽 그림은 가장 일반적인 구조로서, actions, constants, reducers라는 세 개의 디렉터리를 만들고 그 안에 기능별로 파일을 하나씩 만드는 방식이다.
- 코드를 종류에 따라 다른 파일에 작성하여 정리할 수 있어서 편리하지만, 새로운 액션을 만들 때마다 세 종류의 파일을 모두 수정해야 해서 불편함이 잇다.
- 이 방식은 리덕스 공식 문서에서도 사용되므로 가장 기본적이라고 할 수 있지만, 사람에 따라 불편할 수도 있는 구조이다.
- 오른쪽 그림은 액션 타입, 액션 생성 함수, 리듀서 함수를 기능별로 파일 하나에 몰아서 다 작성하는 방식이다.
- ⇒ 이런 방식을 Ducks 패턴이라고 부르며, 앞서 설명한 일반적인 구조로 리덕스를 사용하다가 불편함을 느낀 개발자들이 자주 사용한다.
- 리덕스 관련 코드에 대한 디렉터리 구조는 정해진 방법이 없기 때문에 마음대로 작성해도 되지만, 위 두가지 방법이 주로 사용된다.
액션 타입 정의하기
- Ducks 패턴을 사용하여 액션 타입, 액션 생성 함수, 리듀서를 작성한 코드를 ‘모듈’이라고 한다.
- 가장 먼저 해야 할 작업은 액션 타입을 정의하는 것이다.
- 액션 타입은 대문자로 정의하고, 문자열 내용은
모듈 이름/액션 이름
과 같은 형태로 작성한다. - 문자열 안에 모듈 이름을 넣어줌으로써 나중에 프로젝트가 커졌을 때 액션의 이름이 충돌되지 않게 해준다.
// src/modules/counter.ts
const INCREASE = "counter/INCREASE";
const DECREASE = "counter/DECREASE";
// src/modules/todos.ts
const CHANGE_INPUT = "todos/CHANGE_INPUT"; // input값을 변경함
const INSERT = "todos/INSERT"; // 새로운 todo를 등록
const TOGGLE = "todos/TOGGLE"; // todo를 체크/체크해제 함
const REMOVE = "todos/REMOVE"; // todo를 제거
액션 생성 함수 만들기
- 액션 타입을 정의한 다음에는 액션 생성 함수를 만들어주어야 한다.
// src/modules/counter.ts
const INSERT = "todos/INSERT"; // 새로운 todo를 등록
const TOGGLE = "todos/TOGGLE"; // todo를 체크/체크해제 함
const REMOVE = "todos/REMOVE"; // todo를 제거
// src/modules/todos.ts
export const insert = (text: string) => ({
type: INSERT,
payload: text,
});
export const toggle = (id: number) => ({
type: TOGGLE,
payload: id,
});
export const remove = (id: number) => ({
type: REMOVE,
payload: id,
});
초기 상태 및 리듀서 함수 만들기
- action type과 state type을 정의해서 리듀서 함수의 파라미터의 action과 initailState의 타입으로 각각 지정해준다.
// src/modules/counter.ts
(...)
type CounterAction = ReturnType<typeof increase> | ReturnType<typeof decrease>;
type CounterState = {
count: number;
};
const initailState: CounterState = {
count: 0,
};
const counter = (state = initailState, action: CounterAction) => {
switch (action.type) {
case INCREASE:
return {
count: state.count + 1,
};
case DECREASE:
return {
count: state.count - 1,
};
default:
return state;
}
};
export default counter;
- Todo 객체의 타입을 따로 정의해준다.
// src/types/Todo.ts
type Todo = {
id: number;
text: string;
done: boolean;
};
export default Todo;
// src/modules/todos.ts
import Todo from "../types/Todo";
(...)
type TodosAction =
| ReturnType<typeof insert>
| ReturnType<typeof toggle>
| ReturnType<typeof remove>;
type TodosState = Todo[];
const initailState: TodosState = [];
const todos = (state = initailState, action: TodosAction): TodosState => {
switch (action.type) {
case INSERT:
const nextId = Math.max(...state.map((todo) => todo.id)) + 1;
return state.concat({
id: nextId,
text: action.payload.toString(),
done: false,
});
case TOGGLE:
return state.map((todo) =>
todo.id === action.payload ? { ...todo, done: !todo.done } : todo
);
case REMOVE:
return state.filter((todo) => todo.id !== action.payload);
default:
return state;
}
};
export default todos;
루트 리듀서 만들기
- createStore 함수를 사용하여 스토어를 만들 때는 리듀서를 하나만 사용해야하기 때문에 기존에 만든 리듀서를 하나로 합쳐주어야 한다.
- 이 작업은 리덕스에서 제공하는
combineReducers
라는 유틸 함수를 사용하여 쉽게 처리할 수 있다. - 모듈이 추가될 때마다 rootReducer 안에 추가해주어야 한다.
// src/modules/index.ts
import { combineReducers } from "redux";
import counter from "./counter";
import todos from "./todos";
const rootReducer = combineReducers({
counter,
todos,
});
export type RootState = ReturnType<typeof rootReducer>;
export default rootReducer;
리액트 애플리케이션에 리덕스 적용하기
- 스토어를 만들고 리액트 애플리케이션에 리덕스를 적용하는 작업은 src 디렉터리의 index.tsx에서 이루어진다.
스토어 만들기
- 가장 먼저 스토어를 생성한다.
- createStore를 사용하면 취소선이 그어진다.
안뜨게 하려면
⇒import { legacy_createStore as createStore } from "redux";
이름 바꿔서 사용하기!
출처 | https://velog.io/@201_steve/redux-createStore-deprecated
// index.tsx
(...)
import { legacy_createStore as createStore } from "redux";
import rootReducer from "./modules";
const store = createStore(rootReducer);
Provider 컴포넌트를 사용하여 프로젝트에 리덕스 적용하기
- App 컴포넌트를 react-redux에서 제공하는 Provider 컴포넌트로 감싸준다.
- 이 컴포넌트를 사용할 때는 store를 props로 전달해 주어야 한다.
// index.tsx
import { Provider } from "react-redux";
(...)
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
컨테이너 컴포넌트 만들기
- 컴포넌트에서 리덕스 스토어에 접근하여 원하는 상태를 받아오고, 액션도 디스패치해 줄 차례이다.
- 리덕스 스토어와 연동된 컴포넌트를
컨테이너 컴포넌트
라고 부른다.
CounterContainer 만들기
src/containers
디렉터리를 생성하고 그 안에CounterContainer
컴포넌트를 만든다.
import Counter from "../components/Counter";
const CounterContainer = () => {
return <Counter count={0} />;
};
export default CounterContainer;
- 위 컴포넌트를 리덕스와 연동하려면 react-redux에서 제공하는 connect 함수를 사용해야 하며, 다음과 같이 사용한다.
connect(mapStateProps, mapDispatchToProps)(연동할 컴포넌트)
mapStateProps
: 리덕스 스토어 안의 상태를 컴포넌트의 props로 넘겨주기 위해 설정하는 함수mapDispatchProps
: 액션 생성 함수를 컴포넌트의 props로 넘겨주기 위해 사용하는 함수connect
함수를 호출하고 나면 또 다른 함수를 반환하는데,
반환된 함수에 컴포넌트를 파라미터로 넣어주면 리덕스와 연동된 컴포넌트가 만들어진다.- mapStateToProps와 mapDispatchProps에서 반환하는 객체 내부의 값들은 컴포넌트의 props로 전달된다.
mapStateToProps
는state
를 파라미터로 받아오며, 이 값은 현재 스토어가 지니고 있는 상태를 가르킨다.mapDispatchToProps
의 경우 store의 내장 함수dispatch
를 파라미터로 받아온다.
// src/containers/CounterContainer.tsx
import { connect } from "react-redux";
import Counter from "../components/Counter";
import { RootState } from "../modules";
import { decrease, increase } from "../modules/counter";
import { Dispatch } from "redux";
const CounterContainer = ({
count,
increase,
decrease,
}: {
count: number;
increase: () => void;
decrease: () => void;
}) => {
return <Counter count={count} onIncrease={increase} onDecrease={decrease} />;
};
const mapStateToProps = (state: RootState) => ({
count: state.counter.count,
});
const mapDispatchToProps = (dispatch: Dispatch) => ({
increase: () => {
dispatch(increase());
},
decrease: () => {
dispatch(decrease());
},
});
export default connect(mapStateToProps, mapDispatchToProps)(CounterContainer);
- 기존 App.tsx에서
Counter
를CounterContainer
로 교체한다.
// src/App.tsx
function App() {
return (
<div className="App">
<CounterContainer />
<hr />
<Todos />
</div>
);
}
- +1 버튼과 -1 버튼을 눌렀을 때 아래처럼 제대로 작동하는 것을 확인할 수 있다.
- 일반적으로는 위 코드처럼 mapStateToProps와 mapDispatchToProps를 미리 선언해놓고 사용하지만, 아래처럼 connect 함수 내부에 익명 함수 형태로 선언할 수도 있다.
export default connect(
(state: RootState) => ({
count: state.counter.count,
}),
(dispatch: Dispatch) => ({
increase: () => {
dispatch(increase());
},
decrease: () => {
dispatch(decrease());
},
})
)(CounterContainer);
- 컴포넌트에서 액션을 디스패치하기 위해 각 액션 함수를 호출하고 dispatch로 감싸는 작업이 조금 번거로울 수 있다.
- 특시 액션 생성 함수의 개수가 많아지면 더더욱 그럴 것이다.
- 리덕스에서 제공하는
bindActionCreators
유틸 함수를 사용하면 간편하다. - 두번째 파라미터를 아예 객체 형태로 넣어주면 connect 함수가 내부적으로 bindActionCreator 작업을 해준다.
export default connect(
(state: RootState) => ({
count: state.counter.count,
}),
(dispatch: Dispatch) => bindActionCreators({ increase, decrease }, dispatch)
)(CounterContainer);
TodosContainer 만들기
- 아래와 같이 TodosContainer를 정의하고, CounterContainer와 마찬가지로 App.tsx에서 호출해주어야 한다.
import { connect } from "react-redux";
import Todos from "../components/Todos";
import { RootState } from "../modules";
import Todo from "../types/Todo";
import { Dispatch, bindActionCreators } from "redux";
import { insert, remove, toggle } from "../modules/todos";
const TodosContainer = ({
todos,
insert,
toggle,
remove,
}: {
todos: Todo[];
insert: (text: string) => void;
toggle: (id: number) => void;
remove: (id: number) => void;
}) => {
return (
<Todos
todos={todos}
onInsert={insert}
onToggle={toggle}
onRemove={remove}
/>
);
};
export default connect(
({ todos }: RootState) => ({
todos: todos,
}),
(dispatch: Dispatch) =>
bindActionCreators(
{
insert,
toggle,
remove,
},
dispatch
)
)(TodosContainer);
- 그리고나서 Todos 컴포넌트에서 받아온 props를 사용하도록 구현한다.
// src/components/Todos.tsx
import { useState } from "react";
import Todo from "../types/Todo";
import TodoItem from "./TodoItem";
const Todos: React.FC<{
todos: Todo[];
onInsert: (text: string) => void;
onToggle: (id: number) => void;
onRemove: (id: number) => void;
}> = ({ todos, onInsert, onToggle, onRemove }) => {
const [value, setValue] = useState("");
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
onInsert(value);
setValue("");
};
return (
<div>
<form onSubmit={onSubmit}>
<input value={value} onChange={(e) => setValue(e.target.value)} />
<button type="submit">등록</button>
</form>
{todos?.length === 0 ? (
<div>목록이 비어있습니다.</div>
) : (
<div>
{todos?.map((todo) => (
<TodoItem
todo={todo}
key={todo.id}
onToggle={onToggle}
onRemove={onRemove}
/>
))}
</div>
)}
</div>
);
};
export default Todos;
리덕스 더 편하게 사용하기
- 타입스크립트에서 리덕스를 사용할 때
typesafe-actions
를 활용하면 리덕스를 훨씬 더 편하게 사용할 수 있다.
typesafe-actions
- 액션 생성 함수를 더 짧은 코드로 작성할 수 있다.
- 그리고 리듀서를 작성할 때도 switch-case 문이 아닌
createReducer
라는 함수를 사용하여 각 액션마다 업데이트 함수를 설정하는 형식으로 작성해 줄 수 있다.
라이브러리 설치
npm install typesafe-actions
createStandardAction 함수 사용
- createStandardAction을 사용하면 매번 객체를 직접 만들어 줄 필요 없이 더욱 간단하게 액션 생성 함수를 선언할 수 있다.
// src/modulex/couner.ts
// 기존코드
export const increase = () => ({ type: INCREASE });
export const decrease = () => ({ type: DECREASE });
// createStandardAction 함수 적용
import { ActionType, createReducer, deprecated } from "typesafe-actions";
const { createStandardAction } = deprecated;
(...)
export const increase = createStandardAction(INCREASE)();
export const decrease = createStandardAction(DECREASE)();
// src/modules/todos.ts
// 기존코드
export const insert = (text: string) => ({
type: INSERT,
payload: text,
});
export const toggle = (id: number) => ({
type: TOGGLE,
payload: id,
});
export const remove = (id: number) => ({
type: REMOVE,
payload: id,
});
// createStandardAction 함수 적용
export const insert = createStandardAction(INSERT)<string>();
export const toggle = createStandardAction(TOGGLE)<number>();
export const remove = createStandardAction(REMOVE)<number>();
ActionType 타입 사용
- ActionType을 사용하면 액션들의 객체 타입을 만드는 작업을 더욱 짧은 코드로 작성 할 수 있다.
// 기존 코드
type CounterAction = ReturnType<typeof increase> | ReturnType<typeof decrease>;
// ActionType 사용
const actions = { increase, decrease };
type CounterAction = ActionType<typeof actions>;
// 기존 코드
type TodosAction =
| ReturnType<typeof insert>
| ReturnType<typeof toggle>
| ReturnType<typeof remove>;
// ActionType 사용
const actions = {insert,toggle,remove};
type TodosAction =ActionType<typeof actions>
createReducer 함수 사용
createReducer
를 사용하면 리듀서를switch/case
문이 아닌 object map 형태로 구현 할 수 있어서 코드가 더욱 간결해지고, 타입 지원도 잘 이루어진다.
// src/modulex/counter.ts
// 기존 코드
const counter = (state = initailState, action: CounterAction) => {
switch (action.type) {
case INCREASE:
return {
count: state.count + 1,
};
case DECREASE:
return {
count: state.count - 1,
};
default:
return state;
}
};
// createReducer 사용
const counter = createReducer<CounterState, CounterAction>(initailState, {
[INCREASE]: (state) => ({ count: state.count + 1 }),
[DECREASE]: (state) => ({ count: state.count - 1 }),
});
// 기존 코드
const todos = (state = initailState, action: TodosAction): TodosState => {
switch (action.type) {
case INSERT:
const nextId =
state.length > 0 ? Math.max(...state.map((todo) => todo.id)) + 1 : 1;
return state.concat({
id: nextId,
text: action.payload.toString(),
done: false,
});
case TOGGLE:
return state.map((todo) =>
todo.id === action.payload ? { ...todo, done: !todo.done } : todo
);
case REMOVE:
return state.filter((todo) => todo.id !== action.payload);
default:
return state;
}
};
// createReducer 사용
const todos = createReducer<TodosState, TodosAction>(initailState, {
[INSERT]: (state, { payload: text }) =>
state.concat({
id: state.length > 0 ? Math.max(...state.map((todo) => todo.id)) + 1 : 1,
text,
done: false,
}),
[TOGGLE]: (state, { payload: id }) =>
state.map((todo) =>
todo.id === id ? { ...todo, done: !todo.done } : todo
),
[REMOVE]: (state, { payload: id }) => state.filter((todo) => todo.id !== id),
});
Hooks를 사용하여 컨테이너 컴포넌트 만들기
- 리덕스 스토어와 연동된 컨테이너 컴포넌트를 만들 때 connect 함수를 사용하는 대신 react-redux에서 제공하는 Hooks를 사용할 수도 있다.
useSelector로 상태 조회하기
useSelector
Hook을 사용하면connect
함수를 사용하지 않고도 리덕스의 상태를 조회할 수 있다.useSelector
의 사용법은 아래와 같다.
const 결과 = useSelector(상태 선택 함수);
- 여기서
상태 선택 함수
는mapStateToProps
와 형태가 똑같다. - useDispatch는 컴포넌트 내부에서 스토어의 내장 함수 dispatch를 사용할 수 있게 해준다.
- 컨테이너 컴포넌트에서 액션을 디스패치해야한다면 이 Hook을 사용해야하며, 사용방법은 아래와 같다.
const dispatch = useDispatch()
dispatch({type : "SAMPLE_ACTION:"});
useSelector, useDispatch 사용하기
// src/containers/CounterContainer.tsx
// 기존코드
const CounterContainer = ({
count,
increase,
decrease,
}: {
count: number;
increase: () => void;
decrease: () => void;
}) => {
return <Counter count={count} onIncrease={increase} onDecrease={decrease} />;
};
export default connect(
(state: RootState) => ({
count: state.counter.count,
}),
(dispatch: Dispatch) => bindActionCreators({ increase, decrease }, dispatch)
)(CounterContainer);
// useSelector, useDispatch 사용
const CounterContainer = () => {
const count = useSelector((state: RootState) => state.counter.count);
const dispatch = useDispatch();
return (
<Counter
count={count}
onIncrease={() => dispatch(increase())}
onDecrease={() => dispatch(decrease())}
/>
);
};
- 위와 같이 적용하면 숫자가 바뀌어서 리렌더링될 때마다 onIncrease 함수와 onDecrease 함수가 새롭게 만들어진다.
- 만약 컴포넌트 성능을 최적화해야 하는 상황이 오면
useCallback
으로 액션을 디스패치하는 함수를 감싸주는 것이 좋다.
const CounterContainer = () => {
const count = useSelector((state: RootState) => state.counter.count);
const dispatch = useDispatch();
const onIncrease = useCallback(() => dispatch(increase()), [dispatch]);
const onDecrease = useCallback(() => dispatch(decrease()), [dispatch]);
return (
<Counter count={count} onIncrease={onIncrease} onDecrease={onDecrease} />
);
};
// src/containers/TodosContainer.tsx
// 기존 코드
const TodosContainer = ({
todos,
insert,
toggle,
remove,
}: {
todos: Todo[];
insert: (text: string) => void;
toggle: (id: number) => void;
remove: (id: number) => void;
}) => {
return (
<Todos
todos={todos}
onInsert={insert}
onToggle={toggle}
onRemove={remove}
/>
);
};
export default connect(
({ todos }: RootState) => ({
todos: todos,
}),
(dispatch: Dispatch) =>
bindActionCreators(
{
insert,
toggle,
remove,
},
dispatch
)
)(TodosContainer);
// useSelector, useDispatch,useCallback 사용
const TodosContainer = () => {
const todos = useSelector((state: RootState) => state.todos);
const dispatch = useDispatch();
const onInsert = useCallback(
(text: string) => dispatch(insert(text)),
[dispatch]
);
const onToggle = useCallback(
(id: number) => dispatch(toggle(id)),
[dispatch]
);
const onRemove = useCallback(
(id: number) => dispatch(remove(id)),
[dispatch]
);
return (
<Todos
todos={todos}
onInsert={onInsert}
onToggle={onToggle}
onRemove={onRemove}
/>
);
};
connect 함수 vs. useSelector&useDispatch
connect
함수를 사용하여 컨테이너 컴포넌트를 만들었을 경우, 해당 컨테이너 컴포넌트의 부모 컴포넌트가 리렌더링될 때 해당 컨테이너 컴포넌트의 props가 바뀌지 않았다면 리렌더링이 자동으로 방지되어 성능이 최적화된다.- 반면 useSelecotr를 사용하여 리덕스 상태를 조회했을 때는 이 최적화 작업이 자동으로 이루어지지 않으므로, 성능 최적화를 위해서는 React.memo를 컨테이너 컴포넌트에 사용해주어야 한다.
- 지금과 같이 TodosContainer의 부모 컴포넌트인 App이 리렌더링되는 일이 없는 경우에는 불필요한 성능 최적화이다.
export default React.memo(CounterContainer);
export default React.memo(TodosContainer);
정리
- 리액트 프로젝트에서 리덕스를 사용하면 업데이트에 관련된 로직을 리액트 컴포넌트에서 완벽하게 분리시킬 수 있으므로 유지 보수성이 높은 코드를 작성해낼 수 있다.
- 규모가 작은 프로젝트에서는 오히려 리덕스를 적용했을 때 프로젝트의 복잡도가 높아질 수 있지만,
- 규모가 큰 프로젝트에서는 리덕스를 적용하면 상태를 더 체계적으로 관리할 수 있고, 개발자 경험도 향상시켜준다.
'BOOK > 리액트를 다루는 기술' 카테고리의 다른 글
[ 리액트를 다루는 기술 ] 18장 리덕스 미들웨어를 통한 비동기 작업관리 - redux-thunk (0) | 2023.12.18 |
---|---|
[ 리액트를 다루는 기술 ] 16장 리덕스 라이브러리 이해하기 (0) | 2023.12.14 |
[ 리액트를 다루는 기술 ] 15장 Context API (0) | 2023.12.14 |