๋ฐ์ํ
๐ฃ ๋จผ์ ์ค๋นํ๊ธฐ (๊ณตํต ์ ์ )
1. ํ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น
npm install @reduxjs/toolkit react-redux redux-persist
2. Redux ์คํ ์ด ๋ง๋ค๊ธฐ (src/app/store.ts)
import { configureStore } from '@reduxjs/toolkit'
import todoReducer from '../features/todo/todoSlice'
import authReducer from '../features/auth/authSlice'
import themeReducer from '../features/theme/themeSlice'
import filterReducer from '../features/filter/filterSlice'
// Redux ์คํ ์ด ์์ฑ - ๋ชจ๋ slice๋ฅผ ํ๋๋ก ํฉ์นฉ๋๋ค
export const store = configureStore({
reducer: {
todo: todoReducer,
auth: authReducer,
theme: themeReducer,
filter: filterReducer,
},
})
// ํ์
์คํฌ๋ฆฝํธ์ฉ ํ์
์ ์
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
3. ์ปค์คํ ํ ์ถ๊ฐ (src/app/hooks.ts)
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './store'
// ๋์คํจ์น์ ์
๋ ํฐ๋ฅผ ํ์
์์ ํ๊ฒ ์ฌ์ฉํ๊ธฐ ์ํ ์ปค์คํ
ํ
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
โ 1. Todo ๊ธฐ๋ฅ
๐ src/features/todo/todoSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
// ํ ์ผ ํ๋์ ํ์
์ ์
export interface Todo {
id: string
title: string
completed: boolean
tags?: string[]
}
// ์ํ๋ Todo ๋ฐฐ์ด
const initialState: Todo[] = []
export const todoSlice = createSlice({
name: 'todo',
initialState,
reducers: {
addTodo: (state, action: PayloadAction<Todo>) => {
state.push(action.payload) // ์๋ก์ด ํ ์ผ์ ์ถ๊ฐ
},
toggleComplete: (state, action: PayloadAction<string>) => {
const todo = state.find(t => t.id === action.payload)
if (todo) {
todo.completed = !todo.completed
}
},
deleteTodo: (state, action: PayloadAction<string>) => {
return state.filter(t => t.id !== action.payload)
},
},
})
export const { addTodo, toggleComplete, deleteTodo } = todoSlice.actions
export default todoSlice.reducer
๐ 2. ์ ์ ์ธ์ฆ
๐ src/features/auth/authSlice.ts
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'
// ๋ก๊ทธ์ธ ๋น๋๊ธฐ thunk
export const login = createAsyncThunk(
'auth/login',
async ({ id, pw }: { id: string; pw: string }) => {
// ์์ ์ฉ: ๋ก๊ทธ์ธ ์ฑ๊ณต ์ ์ ์ ์ ๋ณด์ ํ ํฐ ๋ฐํ
return {
user: id,
token: 'token_example',
}
}
)
interface AuthState {
user: string | null
token: string | null
loading: boolean
}
const initialState: AuthState = {
user: null,
token: null,
loading: false,
}
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
logout: (state) => {
state.user = null
state.token = null
},
},
extraReducers: (builder) => {
builder
.addCase(login.pending, (state) => {
state.loading = true
})
.addCase(login.fulfilled, (state, action) => {
state.user = action.payload.user
state.token = action.payload.token
state.loading = false
})
.addCase(login.rejected, (state) => {
state.loading = false
})
},
})
export const { logout } = authSlice.actions
export default authSlice.reducer
๐ 3. ๋คํฌ ๋ชจ๋
๐ src/features/theme/themeSlice.ts
import { createSlice } from '@reduxjs/toolkit'
interface ThemeState {
mode: 'light' | 'dark'
}
const initialState: ThemeState = {
mode: 'light',
}
const themeSlice = createSlice({
name: 'theme',
initialState,
reducers: {
toggleTheme: (state) => {
state.mode = state.mode === 'light' ? 'dark' : 'light'
},
},
})
export const { toggleTheme } = themeSlice.actions
export default themeSlice.reducer
๐ก ์ ์ฉ ๋ฐฉ๋ฒ (Tailwind ๋๋ ํด๋์ค ๋ณ๊ฒฝ)
const mode = useAppSelector(state => state.theme.mode)
useEffect(() => {
document.body.className = mode
}, [mode])
๐ท๏ธ 4. ํ๊ทธ ํํฐ๋ง
๐ src/features/filter/filterSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
interface FilterState {
tags: string[]
}
const initialState: FilterState = {
tags: [],
}
const filterSlice = createSlice({
name: 'filter',
initialState,
reducers: {
setTags: (state, action: PayloadAction<string[]>) => {
state.tags = action.payload
},
},
})
export const { setTags } = filterSlice.actions
export default filterSlice.reducer
๐ก ํ๊ทธ ๊ธฐ์ค์ผ๋ก Todo ํํฐ๋ง
const todos = useAppSelector((state) => state.todo)
const filterTags = useAppSelector((state) => state.filter.tags)
const filteredTodos = todos.filter(todo =>
filterTags.length === 0 || todo.tags?.some(tag => filterTags.includes(tag))
)
๐ฆ ์ ์ฒด ํด๋ ๊ตฌ์กฐ ์์ฝ
src/
โโโ app/
โ โโโ store.ts
โ โโโ hooks.ts
โโโ features/
โ โโโ auth/
โ โ โโโ authSlice.ts
โ โโโ todo/
โ โ โโโ todoSlice.ts
โ โโโ theme/
โ โ โโโ themeSlice.ts
โ โโโ filter/
โ โโโ filterSlice.ts
โโโ components/
โโโ TodoList.tsx
โโโ TodoItem.tsx
โโโ LoginForm.tsx
โจ ๋ค์์ ์ด์ด์ ๊ตฌํํ ์ ์๋ ๊ฒ๋ค
redux-persist๋ก ์ํ ์ ์ง๊ฐ ๊ธฐ๋ฅ์ ์ปดํฌ๋ํธ๋ก ๋ง๋ค๊ธฐ (<TodoList />, <LoginForm />, <TagFilter />)Zustand๋ก ๋์ฒดํ๋ ๋ฐฉ์Firebase ์ธ์ฆ ์ฐ๋
728x90
๋ฐ์ํ
'์น๊ฐ๋ฐ > reactjs' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Redux VS Zustand ์ฐจ์ด์ (0) | 2025.04.19 |
---|---|
Redux + typescript : ํ์ฅ ๊ฐ๋ (0) | 2025.04.14 |
Redux + typescript : ์นด์ดํฐ ์ฆ๊ฐ/๊ฐ์๋ฅผ ๊ตฌํ (0) | 2025.04.14 |
React์์ ํ์ ์คํฌ๋ฆฝํธ ์ฌ์ฉ (0) | 2025.04.14 |
React + TypeScript์์ ์ด๋ฒคํธ(Event) (1) | 2025.04.14 |