Redux 설치
- redux와 react-redux 패키지를 install한다.
- react-redux는 react에서 redux를 편하게 사용하기 위한 context와 hook을 제공한다.
npm install redux
npm install react-redux
redux or store 폴더 생성
- reducer, types, actions, index(reducer, actions의 export를 하나로 합치는 파일)파일을 생성한다.
- rootReducer관련한 root위치에 index파일을 생성한다.
actions 정의
- addTask, removeTask, updateTask과 같은 actions들을 정의한다.
export const addTask = (content: string) => {};
export const updateTask = (
id: string,
contnet: string,
complete: boolean
) => {};
export const removeTask = (id: string) => {};
reducer 정의
- 기존 상태를 의미하는 state와 변화를 위한 action을 받는 reducer를 정의한다.
// state: 기존 상태, action: 변화를 위한 action
export const tasks = (state, action) => {};
types 정의
- reducer에 필요한 state와 action의 type을 지정한다.
- actions의 type은 각 action의 type에 해당하는 ActionTypes으로 지정한다.
- action의 type은 각 type과 payload를 받고 type은 전에 정의해둔 ActionTypes로 지정하고 payload는 state의 type으로 지정한다.
// state에 대한 타입을 정의한다.
export interface Task {
id: string;
content: string;
complete: boolean;
}
// actions에 대한 타입을 정의한다.
export type ActionTypes = "ADD_TASK" | "UPDATE_TASK" | "REMOVE_TASK";
// action에 대한 타입을 정의한다.
// action의 타입은 action들의 type과 payload(실제로 들어가는 값)을 받는다.
export type Action = {
type: ActionTypes,
payload: Task
}
- type정의가 완료되면 reducer에 type을 import하여 부여한다.
import { Action, Task } from "./types";
export const tasks = (state: Task[], action: Action) => {};
reducer 로직 구현
- action에 대한 로직을 switch문으로 구현한다.
export const tasks = (state: Task[] = [], action: Action) => { // Task[] = [] undefined 발생을 막기 위해
switch (action.type) {
case "ADD_TASK": {
const newTask = action.payload;
return [...state, newTask];
}
case "UPDATE_TASK": {
const updatedTask = action.payload;
return state.map((oldTask) =>
oldTask.id === updatedTask.id ? updatedTask : oldTask
);
}
case "REMOVE_TASK": {
const removedTask = action.payload;
return state.filter((task) => task.id !== removedTask.id);
}
default: {
return state;
}
}
};
action 구현
- 정의 해놓은 action을 type과 payload를 return하는 로직을 구현한다.
import { Action } from "./types";
import { v4 } from "uuid";
// reducer에서 사용할 수 있도록 action을 정의한다.
export const addTask = (content: string): Action => {
return {
type: "ADD_TASK",
payload: {
id: v4(),
content,
complete: false,
},
};
};
export const updateTask = (
id: string,
content: string,
complete: boolean
): Action => {
return {
type: "UPDATE_TASK",
payload: {
id,
content,
complete,
},
};
};
export const removeTask = (id: string): Action => {
return {
type: "REMOVE_TASK",
payload: {
id,
content: "",
complete: false,
},
};
};
index.ts
- 구현했던 action과 reducer를 해당 폴더의 index.ts에 모두 export한다.
export * from "./actions";
export * from "./reducer";
root reducer & create store
- 구현 했던 모든 reducer들을 redux폴더의 최상위 root인 index.ts에 rootReducer로 combine한다.
- combine한 reducer들을 기반으로 store를 생성하고 이를 전역 Provider로 전달하게 끔 구현한다.
- createStore에 취소선이 생기는 이유는 redux core가 아닌 redux toolkit으로 구현하는 것을 추천하고 있기 때문이다.
import { createStore } from 'redux';
import { combineReducers } from "redux";
import { tasks } from "./tasks";
// root reducer를 만든다.
// react-redux에서 제공하는 전역 Provider를 통해 store값 들을 App컴포넌트에 전달하기 위해서 reducer들을 전부 합쳐주는 최상위 root인 index.ts에 root reducer을 구현해야한다.
// 모든 reducer들을 combineReducers에 전달하여 합친다.
const rootReducer = combineReducers({ tasks });
// 만든 rootReducer를 가지고 store를 생성하여 export할 수 있게한다.
export const store = createStore(rootReducer)
// RootState를 rootReducer의 타입으로 지정한다.
export type RootState = ReturnType<typeof rootReducer>
- 이후 App컴포넌트를 감싸는 src폴더의 최상위 root인 index.tsx에 전역 Provider로 감싸고 store를 전달한다.
import { Provider } from "react-redux";
import { store } from "./redux";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<Provider store={store}>
<React.StrictMode>
<App />
</React.StrictMode>
</Provider>
);
useDispatch & useSelector
useDispatch
- react-redux에서 제공하는 useDispatch를 사용하여 reducer를 사용할 수 있다.
import { useDispatch } from "react-redux";
import { addTask, updateTask, removeTask } from "../../redux/tasks";
const dispatch = useDispatch()
dispatch(addTask(content));
dispatch(updateTask(id, content, complete))
dispatch(removeTask(id))
useSelector
- react-redux에서 제공하는 useSelector를 사용하여 RootState인 root store의 특정 store를 가져올 수 있다.
import { useSelector } from "react-redux";
import { RootState } from "../../redux";
const tasks = useSelector((store: RootState) => store.tasks);
return (
<UnorderedList {...props}>
{tasks.map((item) => (
<Task
key={item.id}
id={item.id}
content={item.content}
complete={item.complete}
/>
))}
</UnorderedList>
);