[react] TypeScript와 Redux

young-gue Park·2023년 4월 7일
1

React

목록 보기
16/17
post-thumbnail

⚡ TypeScript와 Redux


📌 Redux

  • 자바스크립트 앱을 위한 예측 가능한 상태 컨테이너
  • Reducer(리듀서)가 주문을 보고 Store(스토어)의 상태를 업데이트하는 것
  • Prop Drilling을 해결하기 위해 Redux를 사용한다!
🤔🤔🤔 엥? 이거 완전 Context API 아니냐..? 🤔🤔🤔
Nope!

⭐ Redux만의 특징

Context API: React 내장 API

Redux: Third Party 라이브러리, 개발 편의를 위한 미들웨어 기능을 제공하고 성능 최적화를 제공한다.

  • Context API의 구조와 다르게 Redux는 조금 더 복잡하다.

    • Action이 발생하면 Reducer를 호출하고 Reducer를 통해 Store에 상태를 저장, Store가 변경되면 View는 리렌더링

  • 이는 Context API에 useReducer를 사용한 것과 유사하다.

❗ 하지만 소규모 프로젝트에는 효율성의 문제로 인해 Redux보다 Context API가 더 좋을 수도 있다.

💡 Redux를 프로젝트에 사용하기 위해서 반드시 터미널 명령어를 통한 다운로드가 필요하다.

yarn add redux react-redux

⭐ redux-logger

  • 상태가 변경될 때 console에 로고를 찍어주는 middleware 라이브러리

  • 사용 시에는 타입을 따로 설치해야할 필요가 있다.
yarn add redux-logger
yarn add -D @types/redux-logger // 타입을 따로 설치해야함

⭐ redux-devtools-extension

  • redux의 상태를 보고 추적할 수 있다.

yarn add -D redux-devtools-extension

⭐ redux-presist

  • 상태가 localStorage나 sessionStorage를 통해 남아 있을 수 있도록 도와주는 middleware 라이브러리
yarn add redux-persist

📌 TypeScript와 Redux로 Todolist 리팩토링하기

🔷 이전에 자바스크립트와 리액트로 제작한 Todolist를 타입스크립트와 리덕스를 이용하여 리팩토링 하였다.

  1. 리덕스 사용 이전엔 Context API를 이용하였으나 redux로 리팩토링 하면서 contexts는 필요 없게 되었다.

  2. useLocalStorage 훅도 redux-presist를 사용하면서 필요 없게 되었다.

  3. 타입스크립트를 적용하면서 컴포넌트에 인터페이스를 새로 적용하였다. 그 외엔 컴포넌트에 큰 변화가 없다.

  4. 그래서 이번 리팩토링에서 가장 중요하다고 생각하는 redux 폴더의 파일들만 코드 리뷰를 진행한다.

    나머진 이곳에서 확인해주세용 (Bzeromo's git repogitory)

⭐ 구현 코드

💻 src/redux/tasks/actions.ts

import { v4 } from "uuid"
import { Action } from "./types"

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
        },
    };
};

💻 src/redux/tasks/reducer.ts

import { Action, Task } from "./types"

export const tasks = (state: Task[] = [], action: Action) => {
    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;
        }
    }
};

💻 src/redux/tasks/types.ts

export interface Task {
    id: string;
    content: string;
    complete: boolean;
}

export type ActionType = 'ADD_TASK' | 'UPDATE_TASK' | 'REMOVE_TASK';

export type Action = { type: ActionType, payload: Task };

💻 src/redux/tasks/index.ts

export * from './actions'
export * from './reducer'

💻 src/redux/index.ts

import { applyMiddleware, combineReducers, createStore } from "redux";
import { tasks } from "./tasks";
import logger from "redux-logger"
import { composeWithDevTools } from "redux-devtools-extension";
import storage from "redux-persist"
import session from "redux-persist/lib/storage/session"
import { persistReducer } from "redux-persist";
import persistStore from "redux-persist/es/persistStore";

const persistConfig = {
    key: 'root',
    storage: session,
    whitelist: ['tasks'],
};

const combinedReducer = combineReducers({ tasks });

const rootReducer = persistReducer(persistConfig, combinedReducer)

export const store = createStore(rootReducer, composeWithDevTools(applyMiddleware(logger)));

export const persistor = persistStore(store as any);

export type RootState = ReturnType<typeof rootReducer>;

createStore의 경우 deprecated로 표시되는데 이는 추후에 다룰 redux-toolkitconfigureStore라는 상위호환이 있기 때문이다.


🖨 리팩토링 결과

기능은 이전처럼 잘 작동한다.
진행 상황은 세션 저장소에 저장되어 새로고침을 해도 유지된다.

redux-devtools-extension이 정상 작동하는 모습이다.

💡 redux-devtools-extension의 경우, 이벤트 진행 상황을 뒤로 돌리거나 현재까지의 진행 상황을 일정 시점부터 다시 자동으로 진행시키는 등의 작업 역시 가능하다. 세번째 사진 하단 재생바가 그 기능이다.

redux-presist 덕에 세션 저장소에 잘 저장되는 모습

Redux와 타입스크립트를 이용해 Todolist 리팩토링에 성공하였다.

profile
Hodie mihi, Cras tibi

0개의 댓글