redux-toolkit 공식사이트를 참고하면
Redux-toolkit을 사용해보기 위해 간단하게 카운터와 to-do-list를 만들어 보았다!
폴더구조는 아래와 같이 만들었다!
Store.tsx
configureStore가 reducere를 감싸고 있는데 여기서 모든 state를 관리하고 있다.
const Store = configureStore({
reducer: {
counter: counterReducer,
todos: todosReducer,
},
});
export default Store;
index.tsx
index에 store.tsx를 연결시켜준다!
Provider로 감싸주는데 store가 리액트 앱 전체를 감싸주도록 하는 것이다.
그리고 store 라는 파라미터를 전달해준다.
import React from "react";
import ReactDOM from 'react-dom';
import { Provider } from "react-redux";
import App from "./App.tsx";
import store from "./redux/Store.tsx";
import reportWebVitals from "./reportWebVitals";
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
App.tsx
크게 CounterContainer과 TodoContainer이 있다.
import React from 'react';
import './App.css';
import CounterContainer from './containers/CounterContainer.tsx';
import TodosContainer from './containers/TodosContainer.tsx';
function App() {
return (
<div>
<CounterContainer />
<hr />
<TodosContainer />
</div>
);
}
export default App;
기존에는 createReducer, createAction이 하던 일을 createSlice가 한번에 해준다.
name은 리듀서에 들어갈 이름을 정하고,
initialState는 초기값,
reducers는 상태가 변하면 어떻게 실행될지 정하는 부분이다.
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
// 인터페이스 정의
export interface ICounterState {
number: number;
diff: number;
}
// 초기 상태 선언
const initialState: ICounterState = {
number: 0,
diff: 1, // 차이 값
};
//reducer
const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increase(state, action: PayloadAction<void>) {
state.number = state.number + state.diff;
},
decrease(state, action: PayloadAction<void>) {
state.number = state.number - state.diff;
},
setDiff(state, action: PayloadAction<number>) {
state.diff = action.payload;
},
},
});
// 액션 생성 함수와 리듀서 내보내기
export const { increase, decrease, setDiff } = counterSlice.actions;
export default counterSlice.reducer;
modules/todos.tsx
import { createSlice } from "@reduxjs/toolkit";
// 인터페이스 정의
export interface Todo {
id: number;
text: string;
done: boolean;
}
//초기 상태 선언
const initialState: Todo[] = [];
let nextId = 1;
const todoSlice = createSlice({
name: "todos",
initialState,
reducers: {
ADD_TODO: (state, action) => {
return [...state, action.payload.todo];
},
TOGGLE_TODO: (state, action) => {
return state.map((todo) =>
todo.id === action.payload.id ? { ...todo, done: !todo.done } : todo
);
},
},
});
export const todoActions = todoSlice.actions;
export default todoSlice.reducer;
-> useSelector를 이용하면 위에서 만든 리듀서에 접근할 수 있다.
store.getState() 함수를 호출했을 때 나타나는 결과물과 동일하다.
-> name: "counter" 라고 지어준 리듀서에 있는 state에 접근하는데,
counter 리듀서에 초기값을 number, diff로 설정해놨기 때문에
counter에 있는 number, diff를 가져온다.
-> useDispatch객체를 dispatch로 재선언한 후,
dispatch 변수를 활용하여 액션을 호출하였다.
const result = useSelector((state: IRootState) => state.counter);
console.log를 찍어서 확인해보니 아래와 같이 초기값이 나왔다.
import React, { useCallback } from "react";
import { useSelector, useDispatch } from "react-redux";
import Counter from "../components/Counter.tsx";
import {
increase,
decrease,
setDiff,
ICounterState,
} from "../modules/counter.tsx";
interface IRootState {
counter: ICounterState;
}
const CounterContainer: React.FC = () => {
const { number, diff } = useSelector((state: IRootState) => state.counter);
const dispatch = useDispatch();
// 각 액션들을 디스패치하는 함수들 만듬
const onIncrease = useCallback(() => dispatch(increase()), [dispatch]);
const onDecrease = useCallback(() => dispatch(decrease()), [dispatch]);
const onSetDiff = useCallback(
(diff: number) => dispatch(setDiff(diff)),
[dispatch]
);
return (
<Counter
number={number}
diff={diff}
onIncrease={onIncrease}
onDecrease={onDecrease}
onSetDiff={onSetDiff}
/>
);
};
export default CounterContainer;
import React, { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";
import Todos from "../components/Todos.tsx";
import { Todo, todoActions } from "../modules/todos.tsx";
interface IRootState {
todos: Todo[];
}
const TodosContainer: React.FC = () => {
const todos = useSelector((state: IRootState) => state.todos);
const dispatch = useDispatch();
const onCreate = (text: string) =>
dispatch(
todoActions.ADD_TODO({ todo: { id: Date.now(), text, done: false } })
);
const onToggle = useCallback(
(id: number) => dispatch(todoActions.TOGGLE_TODO({ id })),
[dispatch]
);
return <Todos todos={todos} onCreate={onCreate} onToggle={onToggle} />;
};
export default TodosContainer;
import React, { ChangeEvent } from "react";
interface ICounterProps {
number: number;
diff: number;
onIncrease: () => void;
onDecrease: () => void;
onSetDiff: (diff: number) => void;
}
const Counter: React.FC<ICounterProps> = ({
number,
diff,
onIncrease,
onDecrease,
onSetDiff,
}) => {
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
onSetDiff(parseInt(e.target.value, 10));
};
return (
<div>
<h1>{number}</h1>
<div>
<input type="number" value={diff} min="1" onChange={onChange} />
<button onClick={onIncrease}>+</button>
<button onClick={onDecrease}>-</button>
</div>
</div>
);
};
export default Counter;
import React, { ChangeEvent, FormEvent, useState } from "react";
import { Todo } from "../modules/todos";
interface TodosProps {
todos: Todo[];
onCreate: (text: string) => void;
onToggle: (id: number) => void;
}
const Todos: React.FC<TodosProps> = ({ todos, onCreate, onToggle }) => {
const [text, setText] = useState<string>("");
const onChange = (e: ChangeEvent<HTMLInputElement>) =>
setText(e.target.value);
const onSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault(); // Submit 이벤트 발생했을 때 새로고침 방지
onCreate(text);
setText(""); // 인풋 초기화
};
// 컴포넌트 최적화를 위하여 React.memo를 사용
const TodoItem: React.FC<{ todo: Todo }> = React.memo(({ todo }) => (
<li
style={{ textDecoration: todo.done ? "line-through" : "none" }}
onClick={() => onToggle(todo.id)}
>
{todo.text}
</li>
));
const TodoList: React.FC<{ todos:Todo[] }> = React.memo(({ todos }) => (
<ul>
{todos.map((todo: Todo) => (
<TodoItem key={todo.id} todo={todo} />
))}
</ul>
));
return (
<div>
<form onSubmit={onSubmit}>
<input
value={text}
placeholder="할 일을 입력하세요.."
onChange={onChange}
/>
<button type="submit">등록</button>
</form>
<TodoList todos={todos} />
</div>
);
};
export default Todos;
https://github.com/carrotdy/react-redux-toolkit