- Published on
Redux 기본
- Authors

- Name
- dwook
Table of Contents
Redux 개념
Action
- 상태변화에 대해 알려주는 자바스크립트 객체
- 액션 객체는 type과 payload로 되어 있다.
- 액션 객체는 상태변화에 대한 type을 필수.
- 액션 생성자는 파라미터를 받아 액션 객체를 만든다.
{ type: "ADD_TODO", todo: [] }
{ type: "CHECK_TODO", id: 1 }
Reducer
- 상태와 액션을 가지고 함수를 실행하는 역할
- 리듀서 함수는 2가지 인자를 받음.
- (1) 이전 상태
- (2) 액션 객체
- 리듀서 함수를 실행해서 새로운 상태를 스토어에 업데이트
Dispatch
- 액션을 인자로 받아서 액션을 실행시키는 역할
// 1. 디스패치로 액션을 실행
dispatch(action);
// 2. 리듀서는 이전 상태와 액션 객체를 받아서 스토어 상태를 업데이트
reducer(prevState, action);
Redux 3가지 원칙
- 응용프로그램의 전역상태는 단일 저장소 내의 트리에 저장된다.
- 상태는 읽기 전용이다.
- 순수함수에 의해 변경되어야 한다.
Ducks 패턴
- redux 관련 파일을 구조가 아닌 기능(모듈) 중심으로 나누는 방식
- 액션 이름이 중복되지 않는 선에서
reducer/ACTION_TYPE으로 보통 사용
Ducks 패턴 규칙
- 항상 reducer()란 이름의 함수를
export default해야한다. - 항상 모듈의 액션 생성자들을 함수형태로
export해야한다. - 항상
npm-module-or-app/reducer/ACTION_TYPE형태의 action 타입을 갖는다. - 경우에 따라 action 타입을
UPPER_SNAKE_CASE로 export 할 수 있다.
store/todo.ts
import { TodoType } from '../types/todo';
// 3. 항상 `npm-module-or-app/reducer/ACTION_TYPE` 형태의 action 타입을 갖는다.
// * 액션타입 정의
export const INIT_TODO_LIST = 'todo/INIT_TODO_LIST';
// 2. 항상 모듈의 액션 생성자들을 함수형태로 `export` 해야한다.
// * 액션 생성자 정의
export const setTodo = (payload: TodoType[]) => {
return {
type: INIT_TODO_LIST,
payload,
};
};
export const todoActions = { setTodo };
interface TodoReduxState {
todos: TodoType[];
}
// * 초기 상태
const initialState: TodoReduxState = {
todos: []
};
// 1. 항상 reducer()란 이름의 함수를 `export default` 해야한다.
// * 리듀서
export default function reducer(state = initialState, action: any) {
switch (action.type) {
case SET_TODO_LIST:
const newState = { ...state, todos: action.payload };
return newState;
default:
return state;
}
}
// 참고: 제너릭 방식
import { Reducer } from 'redux';
export default function reducer: Reducer<AppState, Action>() {}
store/index.ts
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { HYDRATE, createWrapper } from 'next-redux-wrapper';
import todo from './todo';
// 각 모듈별 reducer를 combineReducer로 하나로 모음
const rootReducer = combineReducers({
todo,
});
// 합쳐진 reducer에 타입이 '__NEXT_REDUX_WRAPPER_HYDRATE__'인 리듀서를 추가
const reducer = (state, action) => {
if(action.type === HYDRATE) {
const nextState = {
...state,
...action.payload,
}
return nextState;
}
return rootReducer(state, action);
};
// * 스토어 타입: 스토어의 타입을 rootReducer로 부터 얻는다.
export type RootState = ReturnType<typeof rootReducer>;
// * 미들웨어 적용을 위한 스토어 enhancer: 미들웨어에 리덕스 데브툴을 적용
const bindMiddleware = (middleware: any) => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension');
return composeWithDevTools(applyMiddleware(...middleware));
}
return applyMiddleware(...middleware);
};
// 리듀서와 미들웨어로 리덕스 스토어를 리턴.
const initStore = () => {
return createStore(
reducer,
bindMiddleware([])
);
};
// App 컴퍼넌트에서 wrapper로 사용하기 위해 'next-redux-wrapper'에서 createWrapper를 import
export const wrapper = createWrapper(initStore);
- HYDRATE는 서버에서 생성된 리덕스 스토어를 클라이언트에서 사용할 수 있도록 전달해주는 역할
- 리덕스에서 미들웨어는 액션이 디스패치되어 리듀서에서 처리하기 전에 지정된 작업을 하는 것
pages/_app.tsx
import { AppProps } from "next/app";
import GlobalStyle from "../styles/GlobalStyle";
import Header from "../components/Header";
import Footer from "../components/Footer";
import { wrapper } from "../store";
const app = ({ Component, pageProps }: AppProps) => {
return (
<>
<GlobalStyle />
<Header />
<Component {...pageProps} />
<Footer />
</>
);
};
// wrapper를 사용하여 redux 스토어를 컴퍼넌트에 전달
export default wrapper.withRedux(app);
pages/index.tsx
import React from "react";
import { NextPage } from "next";
import TodoList from "../components/TodoList";
import { getTodosAPI } from "../lib/api/todo";
import { wrapper } from "../store";
import { todoActions } from "../store/todo";
const app: NextPage = () => {
return <TodoList />;
};
export const getServerSideProps = wrapper.getServerSideProps(
async ({ store }) => {
console.log(store);
try {
const { data } = await getTodosAPI();
store.dispatch(todoActions.setTodo(data));
// todoActions.setTodo()는 액션 생성자로 액션 객체를 리턴.
return { props: { todos: data } };
} catch (e) {
console.log(e);
return { props: {} };
}
}
);
export default app;
- wrapper를 import하여 기존의 getServerSideProps를 wrapper.getServerSideProps()로 감싸주어 store라는 값을 받을 수 있게 됨.
- store의 getState()를 이용하여 스토어 상태를 불러오고, store의 dispatch()를 사용하여 액션을 디스패치.
참조링크