dwook.record
Published on

Redux 기본

Authors
  • avatar
    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가지 원칙

  1. 응용프로그램의 전역상태는 단일 저장소 내의 트리에 저장된다.
  2. 상태는 읽기 전용이다.
  3. 순수함수에 의해 변경되어야 한다.

Ducks 패턴

  • redux 관련 파일을 구조가 아닌 기능(모듈) 중심으로 나누는 방식
  • 액션 이름이 중복되지 않는 선에서 reducer/ACTION_TYPE으로 보통 사용

Ducks 패턴 규칙

  1. 항상 reducer()란 이름의 함수를 export default 해야한다.
  2. 항상 모듈의 액션 생성자들을 함수형태로 export 해야한다.
  3. 항상 npm-module-or-app/reducer/ACTION_TYPE 형태의 action 타입을 갖는다.
  4. 경우에 따라 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()를 사용하여 액션을 디스패치.

참조링크