일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 자바 기본타입
- Kotlin 조건문
- 파이썬 클래스
- Kotlin 클래스 속성정의
- activate 오류
- 클래스 속성
- 파이썬 장고
- 강제 타입변환
- Python Class
- git
- NextJs
- 좋은글
- 파이썬 반복문
- 다중조건문
- Kotlin 클래스
- 도전
- Python
- python Django
- django virtualenv
- Kotlin Class
- 파이썬 제어문
- 파이썬
- github
- 넥스트js
- Variable declaration
- Kotlin If
- 성공
- 희망
- 장고 가상환경
- Kotlin else if
- Today
- Total
키모스토리
#18 Redux-Toolkit (2) 본문
디렉토리 구조 (JavaScript 버전)
/src
├── app/
│ ├── store.js # Redux store 설정
│ ├── hooks.js # Custom hooks (useAppDispatch, useAppSelector)
│
├── features/ # 기능(도메인)별 상태 및 컴포넌트 관리
│ ├── auth/ # 인증 관련 기능
│ │ ├── authSlice.js # Redux Slice
│ │ ├── authAPI.js # API 요청 관리 (RTK Query 또는 fetch/axios)
│ │ ├── AuthPage.js # 관련 페이지 컴포넌트
│ │ └── components/ # 관련 UI 컴포넌트
│ │ ├── LoginForm.js
│ │ └── SignupForm.js
│ │
│ ├── todos/ # Todo 리스트 관련 기능
│ │ ├── todosSlice.js
│ │ ├── todosAPI.js
│ │ ├── TodosPage.js
│ │ └── components/
│ │ ├── TodoItem.js
│ │ └── TodoList.js
│
├── components/ # 공용 UI 컴포넌트 (버튼, 모달 등)
│ ├── Button.js
│ ├── Modal.js
│ ├── Input.js
│
├── pages/ # 라우트 기반 페이지 관리
│ ├── HomePage.js
│ ├── AboutPage.js
│ ├── NotFoundPage.js
│ ├── Router.js # React Router 설정
│
├── services/ # 공통 서비스 (예: API 클라이언트)
│ ├── axiosInstance.js # Axios 글로벌 인스턴스 설정
│ ├── storage.js # 로컬 스토리지 관련 유틸
│ ├── authService.js # 인증 관련 로직
│
├── hooks/ # 공통 훅 (예: useAuth, useFetch)
│ ├── useAuth.js
│ ├── useTheme.js
│
├── utils/ # 유틸리티 함수
│ ├── formatDate.js
│ ├── debounce.js
│
├── assets/ # 이미지, SVG, 아이콘 등
├── styles/ # 글로벌 스타일 및 테마
│ ├── global.css
│
├── index.js
├── App.js
└── vite.config.js
Redux 상태 관리 예제 (JS 버전)
1️⃣ Redux Slice ( /src/features/todos/ todosSlice.js)
import { createSlice } from "@reduxjs/toolkit";
const initialState = { list: [] };
const todosSlice = createSlice({
name: "todos",
initialState,
reducers: {
addTodo: (state, action) => {
state.list.push({ id: Date.now(), text: action.payload, completed: false });
},
toggleTodo: (state, action) => {
const todo = state.list.find(todo => todo.id === action.payload);
if (todo) {
todo.completed = !todo.completed;
}
},
removeTodo: (state, action) => {
state.list = state.list.filter(todo => todo.id !== action.payload);
},
},
});
export const { addTodo, toggleTodo, removeTodo } = todosSlice.actions;
export default todosSlice.reducer;
2️⃣ Store 설정 (/src/app/store.js)
import { configureStore } from "@reduxjs/toolkit";
import todosReducer from "../features/todos/todosSlice";
export const store = configureStore({
reducer: {
todos: todosReducer,
},
});
3️⃣ useAppDispatch & useAppSelector (/src/app/hooks.js)
- 반복코드, 확장, 유지보수를 위해 분리해서 관리.
import { useDispatch, useSelector } from "react-redux";
export const useAppDispatch = () => useDispatch();
export const useAppSelector = useSelector;
4️⃣ Redux 상태 사용하는 컴포넌트 (/src/features/todos/componets/TodoList.js)
import { useAppSelector, useAppDispatch } from "../../app/hooks";
import { toggleTodo, removeTodo } from "./todosSlice";
const TodoList = () => {
const todos = useAppSelector(state => state.todos.list);
const dispatch = useAppDispatch();
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
<span
onClick={() => dispatch(toggleTodo(todo.id))}
style={{ textDecoration: todo.completed ? "line-through" : "none" }}>
{todo.text}
</span>
<button onClick={() => dispatch(removeTodo(todo.id))}>삭제</button>
</li>
))}
</ul>
);
};
export default TodoList;
위 예제코드에서 useAppDispatch & useAppSelector를 hooks.js 로 분리하는 이유?
1️⃣ 반복되는 코드 최소화
Redux를 사용할 때 useDispatch와 useSelector는 직접 사용해도 되지만,
매번 useSelector(state => state.todos) 같은 패턴을 반복해야 합니다.
특히 TypeScript를 사용할 경우 useSelector의 타입을 명시해야 하는데,
이를 hooks.js에서 한 번만 정의하면 매번 타입을 지정할 필요가 없습니다.
✅ 예제: hooks.js를 분리하지 않은 경우 (반복되는 코드 많음)
import { useDispatch, useSelector } from "react-redux";
import { addTodo } from "../features/todos/todosSlice";
const TodoList = () => {
const dispatch = useDispatch();
const todos = useSelector(state => state.todos.list);
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
<span>{todo.text}</span>
<button onClick={() => dispatch(addTodo("New Task"))}>추가</button>
</li>
))}
</ul>
);
};
- useDispatch()와 useSelector()가 여러 컴포넌트에서 반복됨
- 상태 구조가 변경될 경우, 여러 파일에서 수정해야 함
✅ 예제: hooks.js에서 분리한 경우 (중복 최소화)
// hooks.js
import { useDispatch, useSelector } from "react-redux";
export const useAppDispatch = () => useDispatch();
export const useAppSelector = useSelector;
// TodoList.js
import { useAppDispatch, useAppSelector } from "../../app/hooks";
import { addTodo } from "./todosSlice";
const TodoList = () => {
const dispatch = useAppDispatch();
const todos = useAppSelector(state => state.todos.list);
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
<span>{todo.text}</span>
<button onClick={() => dispatch(addTodo("New Task"))}>추가</button>
</li>
))}
</ul>
);
};
- useAppDispatch()와 useAppSelector()를 사용하여 코드를 단순화
- 상태 구조가 변경되더라도 hooks.js만 수정하면 됨
2️⃣ Redux 미들웨어 확장 용이
Redux 미들웨어(예: redux-thunk, redux-saga)를 사용할 경우 dispatch를 확장해야 하는 경우가 있음.
이를 hooks.js에서 미리 분리해 놓으면 필요할 때 쉽게 확장 가능함.
✅ 예제: dispatch에 미들웨어 적용이 필요한 경우
import { useDispatch } from "react-redux";
// 커스텀 dispatch를 적용할 수도 있음
export const useAppDispatch = () => {
const dispatch = useDispatch();
return (action) => {
console.log("Dispatching action:", action);
return dispatch(action);
};
};
- 모든 dispatch 호출 전에 로그를 찍도록 수정 가능
- 프로젝트 요구사항에 맞게 useAppDispatch를 한 곳에서 수정하면 전체 적용 가능
3️⃣ 코드 일관성 유지 & 유지보수성 증가
모든 Redux 관련 훅을 한 곳(hooks.js)에서 관리하면 다음과 같은 장점이 있음.
- Redux 사용 방식이 프로젝트 전반에서 일관성 유지됨
- 상태 구조 변경 시 한 곳에서 수정 가능 (state.todos.list → state.tasks.items 같은 변경)
- 새 기능을 추가할 때 Redux 적용이 쉬움 (그냥 useAppSelector(state => state.XXX) 사용하면 됨)
✅ 결론
useAppDispatch와 useAppSelector를 hooks.js에서 분리하면
✔ 반복 코드가 줄어들고,
✔ Redux 미들웨어 적용이 쉬워지며,
✔ 상태 변경 시 유지보수가 편리
그래서 대부분의 Redux Toolkit을 사용하는 프로젝트에서는 이 방식이 권장됨!
⚠️ useEffect에서 dispatch를 의존성 배열에 포함해야 하는 이유
경고 메시지:
React Hook useEffect has a missing dependency: 'dispatch'.
Either include it or remove the dependency array react-hooks/exhaustive-deps
App.js
import { useEffect } from "react";
import Navbar from "./components/Navbar";
import { useAppDispatch, useAppSelector } from './app/hooks';
import { calculateTotals } from "./features/cars/carSlice";
import CarLists from "./features/cars/components/CarLists";
function App() {
const {carModels} = useAppSelector((store)=>store.cars);
const dispatch = useAppDispatch();
useEffect(()=>{
dispatch(calculateTotals())
},[carModels]); // dispatch를 뺀 경우, [carModels, dispatch]
return (
<>
<Navbar/>
<CarLists/>
</>
);
}
export default App;
경고 메시지:
React Hook useEffect has a missing dependency: 'dispatch'.
Either include it or remove the dependency array react-hooks/exhaustive-deps
🔹 해결 방법
✅ dispatch를 의존성 배열에 추가
dispatch(calculateTotals());
}, [carModels, dispatch]); // dispatch 추가
🔹 왜 dispatch를 의존성 배열에 추가해야 할까?
React의 useEffect는 의존성 배열에 있는 값이 변경될 때마다 다시 실행됩니다.
- useAppDispatch()는 내부적으로 불변성을 유지하는 함수이므로 값이 바뀌지 않지만,
- ESLint는 dispatch가 외부 스코프에서 가져온 값이므로 안전성을 위해 의존성 배열에 포함할 것을 권장합니다.
실제로 dispatch는 Redux의 store.dispatch를 참조하기 때문에 불변입니다.
하지만 exhaustive-deps 규칙을 따르려면 의존성 배열에 넣는 것이 좋습니다.
🔹 dispatch를 의존성 배열에 포함하면 발생할 문제는 없을까?
❌ 문제 없음
- Redux의 dispatch는 불변 함수이므로, 의존성 배열에 추가해도 useEffect가 불필요하게 실행되지 않습니다.
- ESLint 경고를 피하면서 안전한 코드 작성 가능.
✅ 정리
- dispatch는 불변 함수지만, exhaustive-deps 규칙을 따르기 위해 의존성 배열에 추가하는 것이 권장됨
- dispatch를 의존성 배열에 포함해도 불필요한 재렌더링은 발생하지 않음
- useEffect는 carModels이 변경될 때만 실행됨 (기존 동작과 동일)
👉 결론: [carModels, dispatch]로 설정하는 것이 가장 안전하고 권장되는 방법! 🚀
'Web Devlopment > ReactJs' 카테고리의 다른 글
#19 Rest API - axios (0) | 2025.03.26 |
---|---|
#17 Redux Toolkit (0) | 2025.03.25 |
#16 Flux패턴 (0) | 2025.03.25 |
#15 React Bootstrap (0) | 2025.03.25 |
#14 react-router-dom (0) | 2025.03.24 |