ํ‹ฐ์Šคํ† ๋ฆฌ ๋ทฐ

Issue

 

ํ”„๋กœ์ ํŠธ ๋„์ค‘ typescript ๊ธฐ๋ฐ˜์˜ react-redux ์ „์—ญ ์ƒํƒœ๋ฅผ ์„ธํŒ…ํ•ด์•ผ ๋˜๋Š” ์ƒํ™ฉ์ด ์žˆ์—ˆ๋‹ค.

 

redux ๋‚ด action, ์•ก์…˜ ์ƒ์„ฑ ํ•จ์ˆ˜, ๋ฆฌ๋“€์„œ์— ๋Œ€ํ•œ ์ ์ ˆํ•œ ํƒ€์ž…์„ ์ง€์ •ํ•ด์ฃผ๊ธฐ ๊นŒ๋‹ค๋กœ์šด ์ƒํ™ฉ ์†์—์„œ typesafe-actions ๋ผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค.

 

https://github.com/piotrwitek/typesafe-actions

 

GitHub - piotrwitek/typesafe-actions: Typesafe utilities for "action-creators" in Redux / Flux Architecture

Typesafe utilities for "action-creators" in Redux / Flux Architecture - GitHub - piotrwitek/typesafe-actions: Typesafe utilities for "action-creators" in Redux / Flux Architecture

github.com

 

์ด์— typesafe-actions ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ฑ„ํŒ… ๋กœ๊ทธ๋ฅผ ์ €์žฅํ•˜๋Š” ์ „์—ญ store์„ ๊ตฌํ˜„ํ•˜์˜€๊ณ  ์ด์— ๋Œ€ํ•ด ํšŒ๊ณ ํ•ด ๋ณด๊ณ ์ž ํ•œ๋‹ค.

 

Code

 

1) redux ์ƒํƒœ ๊ด€๋ฆฌ ์ฝ”๋“œ ๋ถ„๋ฆฝ

 

flux ๊ธฐ๋ฐ˜์˜ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ์œ„ํ•ด redux์™€ ๊ด€๋ จ๋œ ์ฝ”๋“œ๋ฅผ modules ํด๋”์— ๋ถ„๋ฆฝํ•˜์—ฌ ์ฃผ์—ˆ๋‹ค.

(module์ด๋ž€ ํ‚ค์›Œ๋“œ๋Š” ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์ƒ์—์„œ ๊ด‘๋ฒ”์œ„ํ•˜๊ฒŒ ์“ฐ์ด๊ธฐ ๋•Œ๋ฌธ์—, ์ง€๊ธˆ ๋ณด๋‹ˆ modules ํด๋”๋ช…์€ ๊ทธ๋‹ฅ ์ข‹์ง€ ๋ชปํ•œ ๊ฒƒ ๊ฐ™๋‹ค)

 

์ฑ„ํŒ… ๊ด€๋ จ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณ  ์žˆ๋Š” comments ํด๋” ๋‚ด์— ํฌ๊ฒŒ actions, reducer, types ํ•˜์œ„ ํด๋”๋กœ ๋ถ„๋ฆฝํ•ด์คŒ์œผ๋กœ์จ, ๊ฐ ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ์ฝ”๋“œ๋ฅผ ๊ฐ„๋žต ๋ฐ ๋ช…๋ชฉํ™” ์‹œ์ผฐ๋‹ค.

๐Ÿ“ฆmodules
 โ”ฃ ๐Ÿ“‚comments
 โ”ƒ โ”ฃ ๐Ÿ“œactions.ts
 โ”ƒ โ”ฃ ๐Ÿ“œreducer.ts
 โ”ƒ โ”— ๐Ÿ“œtypes.ts
 โ”— ๐Ÿ“œindex.ts

 

2) ์•ก์…˜ ์ƒ์„ฑ ํ•จ์ˆ˜์— ๋Œ€ํ•ด createAction ํ•จ์ˆ˜ ์‚ฌ์šฉ

import { createAction } from 'typesafe-actions';
import type { CommentInfo } from './types';

export const ADD_COMMENT = 'comments/ADD_COMMENT' as const;
export const DELETE_COMMENT = 'comments/DELETE_COMMENT';
export const RESPONSE_COMMENT = 'response/RESPONSE_COMMENT';

let nextId = 5;

export const addComment = (commentInfo: CommentInfo) => ({
  type: ADD_COMMENT,
  payload: {
    ...commentInfo,
    messageId: ++nextId,
  },
});

export const deleteComment = createAction(DELETE_COMMENT)<number>();
export const responseComment = createAction(RESPONSE_COMMENT)<number | null>();

์›๋ž˜ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ๋‚ด์—์„œ ์•ก์…˜ ๊ฐ’์— ๋’ค์— as const ๋ผ๊ณ  const assertion์„ ํ•ด์ค˜์•ผ, string literal type์ด ์•„๋‹Œ ์ƒ์ˆ˜ ๊ทธ ์ž์ฒด ๊ฐ’์œผ๋กœ ์ธ์‹๋œ๋‹ค.

 

typesafe-acions ๋‚ด createAction ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์•ก์…˜์„ ์ง€์ •ํ•˜๋ฉด ์ž๋™์œผ๋กœ ์ด์— ๋Œ€ํ•œ ํƒ€์ž… ์ง€์ • ๊ณผ์ •์„ ์ฒ˜๋ฆฌํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ์ƒ๋žต์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

 

ํ•˜์ง€๋งŒ ์ด ๊ฒฝ์šฐ์—๋„ as const๋ฅผ ์จ์•ผ ๋  ๊ฒฝ์šฐ์™€ ์“ฐ์ง€ ์•Š์„ ๊ฒฝ์šฐ๋กœ ๋‚˜๋ˆ ์ง€๋Š”๋ฐ, action.payload ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ์“ฐ์ง€ ์•Š๊ณ  ์ •์ œํ•ด์•ผ ๋  ๊ฒฝ์šฐ(ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›์•„ ์˜จ ๋ฐ์ดํ„ฐ์™€ ๋ฆฌํ„ด๋˜๋Š” action.payload ๊ฐ’์ด ๋‹ค๋ฅผ ๊ฒฝ์šฐ)์—๋Š” createAction ํ•จ์ˆ˜๋ฅผ ์“ฐ์ง€ ์•Š๊ณ  const assertion์„ ํ•œ ๋’ค ์ผ๋ฐ˜ ์•ก์…˜ ์ƒ์„ฑ ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

// ์˜ˆ์‹œ ์ฝ”๋“œ

// const assertion ์‚ฌ์šฉ
export const addTodo = (text: string) => ({
    type: ADD_TODO,
    payload: {
        id: nextId++,
        text
    }
})

// createAction ํ•จ์ˆ˜ ์‚ฌ์šฉ => ์ฝ”๋“œ๊ฐ€ ๋” ๋ณต์žกํ•ด ์งˆ ์ˆ˜ ์žˆ์Œ
export const addTodo = createAction(ADD_TODO, action => (text: string) => action({
     id: nextId++,
     text
}))

 

3) InitialState์™€ Action์— ๋Œ€ํ•œ ํƒ€์ž… ์ง€์ •

import { ActionType } from 'typesafe-actions';
import * as actions from './actions';

export type CommentsAction = ActionType<typeof actions>;
export type ResponseAction = ActionType<typeof actions>;

export type CommentInfo = {
  userid: number;
  messageId: number;
  userName: string;
  profileImage: string;
  content: string;
  date: string;
  responseId: number | null;
};

export type CommentsInfoState = CommentInfo[];

export type ResponseState = {
  responseId: null | number;
  responseActive: boolean;
};

์•ก์…˜์— ๋Œ€ํ•œ ํƒ€์ž…์„ ์ง€์ •ํ•ด ์ฃผ๊ธฐ ์œ„ํ•ด acions ๋ชจ๋“ˆ์—์„œ ๊ฐ action๋“ค์„ ํ•œ ๋ฒˆ์— import ํ•ด ์˜จ ๋’ค, typeof ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•ด๋‹น acions ๊ฐ์ฒด์— ๋Œ€ํ•œ ํƒ€์ž… ์ถ”๋ก ์„ ํ•ด์ฃผ์—ˆ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ํ•ด๋‹น typeof ์—ฐ์‚ฐ์„ typesafe-actions ๋‚ด ActionType ํ•จ์ˆ˜์˜ ์ œ๋„ˆ๋ฆญ์œผ๋กœ ์ง€์ •ํ•ด์ฃผ์–ด, action์— ๋Œ€ํ•œ ํƒ€์ž… ๊ฐ’์„ ์† ์‰ฝ๊ฒŒ ์ง€์ •ํ•˜์—ฌ ์ฃผ์—ˆ๋‹ค.

// ์˜ˆ์‹œ ์ฝ”๋“œ

// ActionType์„ ์“ฐ์ง€ ์•Š์„ ๊ฒฝ์šฐ(๋ชจ๋“  ์•ก์…˜์— ๋Œ€ํ•ด ReturnType ์—ฐ์‚ฐ์„ ์‚ฌ์šฉ)
type TodosAction = ReturnType<typeof addTodo> | ReturnType<typeof toggleTodo> | ReturnType<typeof removeTodo>

// ActionType์„ ์“ธ ๊ฒฝ์šฐ
const actions = { addTodo, toggleTodo, removeTodo };
type TodosAction = ActionType<typeof actions>;

 

4) createReducer ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฆฌ๋“€์„œ ์ •์˜

import { createReducer } from 'typesafe-actions';
import { CommentsAction, CommentsInfoState } from './types';
import { ADD_COMMENT, DELETE_COMMENT, RESPONSE_COMMENT } from './actions';

const initialState: CommentsInfoState = [
  {
    userid: 1,
    messageId: 1,
    userName: '์ถ˜์‹',
    profileImage: './img/profile_yongwoo.png',
    content: '๋„ค์ด๋ฒ„๋กœ ๋ณด๋‚ด์ค˜~',
    date: '2021-02-10 23:26:30',
    responseId: null,
  },
  ( ... )
];

const comments = createReducer<CommentsInfoState, CommentsAction>(initialState, {
  [ADD_COMMENT]: (state, action) => state.concat(action.payload),
  [DELETE_COMMENT]: (state, action) => state.filter((info) => info.messageId !== action.payload),
});

export default comments;

types ๋ชจ๋“ˆ์—์„œ ์ •์˜ ํ•œ type ๊ฐ’๋“ค์„ importํ•˜์—ฌ initialState์— ํƒ€์ž…์„ ์ง€์ •ํ•ด์ฃผ๋ฉฐ createReducer ํ•จ์ˆ˜์— ์ œ๋„ˆ๋ฆญ์œผ๋กœ ์‚ฌ์šฉํ•˜์˜€๋‹ค.

 

// ์˜ˆ์‹œ ์ฝ”๋“œ

// createReducer ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ
function todos(state: TodosState = initialState, action: TodosAction): TodosState {
     switch(action.type) {
         case ADD_TODO:
             return state.concat({
                 id: action.payload.id,
                 text: action.payload.text,
                 done: false,
             })
         case TOGGLE_TODO:
             return state.map(todo =>
                 todo.id === action.payload ? { ...todo, done: !todo.done } : todo
                 )
         case REMOVE_TODO:
             return state.filter(todo => todo.id !== action.payload)
         default:
             return state
     }
 }
 
 // createReducer ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•œ ๊ฒฝ์šฐ
 const todos = createReducer<TodosState, TodosAction>(initialState, {
    [ADD_TODO]: (state, action) => state.concat({
        ...action.payload,
        done: false
    }),
    [TOGGLE_TODO]: (state, action) => state.map(
        todo => todo.id === action.payload ? { ...todo, done: !todo.done } : todo
    ),
    [REMOVE_TODO]: (state, action) => state.filter(todo => todo.id !== action.payload)
})
๋Œ“๊ธ€
๊ณต์ง€์‚ฌํ•ญ
์ตœ๊ทผ์— ์˜ฌ๋ผ์˜จ ๊ธ€
์ตœ๊ทผ์— ๋‹ฌ๋ฆฐ ๋Œ“๊ธ€
Total
Today
Yesterday
๋งํฌ
TAG
more
ยซ   2024/07   ยป
์ผ ์›” ํ™” ์ˆ˜ ๋ชฉ ๊ธˆ ํ† 
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
๊ธ€ ๋ณด๊ด€ํ•จ