๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

TypeScript

TypeScript ์™€ Redux ๋ฅผ ์ด์šฉํ•˜์—ฌ Todo-List ๋งŒ๋“ค๊ธฐ (Feat. typesafe-actions)

TypeScript์˜ ๊ธฐ์ดˆ๋ฅผ ์•Œ์•„๋ณด์•˜๊ณ  Redux์˜ ์‚ฌ์šฉ๋ฒ•๋„ ์•Œ์•˜์œผ๋‹ˆ TypeScript์™€ Redux๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Todo-List๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž 

 

์ด ๊ฒŒ์‹œ๊ธ€์—์„œ๋Š” TypeScriptํ™˜๊ฒฝ์—์„œ Redux๋ฅผ ๋”์šฑ ํŽธํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด typesafe-actions ๋ผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ

์‚ฌ์šฉํ•œ๋‹ค Redux์—์„œ๋Š” redux-actions๋ผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์žˆ์ง€๋งŒ TypeScript ํ™˜๊ฒฝ์—์„œ๋Š” ์‚ฌ์šฉํ•˜๊ธฐ ์ข‹์ง€ ์•Š๋‹ค

 

 

 

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

 

์ด ํ”„๋กœ์ ํŠธ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃจ๋Š” ๋ถ€๋ถ„ Container ์ปดํฌ๋„ŒํŠธ์™€ ํ™”๋ฉด์„ ํ‘œํ˜„ํ•˜๋Š” ๋ถ€๋ถ„ Presentational ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌ๋ถ„ํ•˜์—ฌ ๊ฐœ๋ฐœํ•˜์˜€๋‹ค. ์•„๋ž˜๋ฅผ ํ†ตํ•ด ์‚ดํŽด๋ณด์ž 

 

components ํด๋”์—๋Š” Presentational ์ปดํฌ๋„ŒํŠธ๋“ค์ด , containers ํด๋”์—๋Š” container ์ปดํฌ๋„ŒํŠธ, ๊ทธ๋ฆฌ๊ณ  modules ํด๋”์—๋Š” ๋ฆฌ๋•์Šค ๋ชจ๋“ˆ์„ ๋„ฃ๋Š” ๊ตฌ์กฐ๋กœ ์ง„ํ–‰ํ—€๋‹ค.

 

 

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜

redux ๋ฅผ ์„ค์น˜ํ•˜๋ฉด TypeScript๊ฐ€ ์ž์ฒด์ ์œผ๋กœ ์ง€์›๋˜์ง€๋งŒ react-redux์˜ ๊ฒฝ์šฐ์—๋Š” ์ง€์›์ด ๋˜์ง€ ์•Š๊ธฐ๋•Œ๋ฌธ์— ํŒจํ‚ค์ง€๋ช… ์•ž์—

@types ๋ฅผ ๋ถ™์—ฌ์„œ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•ด์•ผ ํ•œ๋‹ค 

 

$ yarn create-react-app ts-react-app TS-Redux-Todo --typescript
$ cd TS-Redux-Todo
$ yarn add redux react-redux @types/react-redux

 

@types  ๋Š” TypeScript๋ฅผ ์ง€์›ํ•˜์ง€์•Š๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— TypeScript๋ฅผ ์ง€์› ๋ฐ›์„ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‹ค

 

์ด์ œ typesafe-actions ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•ด๋ณด์ž 

 

$ yarn add typesafe-actions

 

๋ฆฌ๋•์Šค ๋ชจ๋“ˆ ์ƒ์„ฑํ•˜๊ธฐ 

 

์šฐ์„  ์ฝ”๋“œ์˜ ์ตœ์ƒ๋‹จ์— ์•„๋ž˜์™€ ๊ฐ™์€ ์œ ํ‹ธ ํ•จ์ˆ˜ ๋ฐ ํƒ€์ž…์„ ๋ถˆ๋Ÿฌ์˜ค์ž

 

 

import {
  ActionType,
  createAction,
  createReducer 
} from 'typesafe-actions';

 

 

Interface ์„ค์ •ํ•˜๊ธฐ

 

interface TodoItemDataParams {
  id: number;
  text: string;
  done: boolean;
}

interface ToDosState {
  todoItems: TodoItemDataParams[];
}

 

๋ฆฌ๋“€์„œ์—์„œ ์•ก์…˜์˜ ํŽ˜์ด๋กœ๋“œ๋‚˜ initialState์˜ ํƒ€์ž…์„ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด interface๋ฅผ ์„ค์ •ํ•ด์ค€๋‹ค. 

 

 

์•ก์…˜ํƒ€์ž… ์„ ์–ธ 

 

const SUBMIT = 'todo/SUBMIT';
const REMOVE = 'todo/REMOVE';
const TOGGLE = 'todo/TOGGLE';

 

typesafe-actions๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด const INCREASE = 'counter/INCREASE' as const;  ์™€ ๊ฐ™์ด as const๋ฅผ ๋ถ™์—ฌ์คฌ์–ด์•ผ ํ•˜์ง€๋งŒ typesafe-actions๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ๋ถ™์ผ ํ•„์š”๊ฐ€ ์—†๋‹ค

 

 

์•ก์…˜ ์ƒ์„ฑ ํ•จ์ˆ˜ ๋งŒ๋“ค๊ธฐ 

 

์•ก์…˜ ์ƒ์„ฑ ํ•จ์ˆ˜๋ฅผ ์„ ์–ธ ํ•  ๋•Œ๋Š” createAction ์ด๋ผ๋Š” ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

 

 

export const submit = createAction(SUBMIT)<TodoItemDataParams>(); 
//๊ฐ์ฒด๋ฅผ ํŽ˜์ด๋กœ๋“œ๋กœ ๋ฐ›์•„์˜ค๊ธฐ ๋•Œ๋ฌธ์— interface์˜ TodoItemDataParams๋ฅผ ํƒ€์ž…์œผ๋กœ ์ง€์ • 
export const remove = createAction(REMOVE)<number>();
export const toggle = createAction(TOGGLE)<number>();

์•ก์…˜ ์ƒ์„ฑํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค ๋•Œ ํŽ˜์ด๋กœ๋“œ๋กœ ๋“ค์–ด๊ฐ€๋Š” ๊ฐ’์€ Generic์œผ๋กœ ์ •ํ•ด์ค„ ์ˆ˜ ์žˆ๋Š”๋ฐ, ๋งŒ์•ฝ ํŽ˜์ด๋กœ๋“œ์— ๋“ค์–ด๊ฐ€๋Š” ๊ฐ’์ด ์—†๋‹ค๋ฉด Generic์„ ์ƒ๋žตํ•ด๋„ ๋œ๋‹ค ์œ„์™€ ๊ฐ™์€ ๊ฒฝ์šฐ๋Š” submit์˜ ํŽ˜์ด๋กœ๋“œ๋กœ ๋“ค์–ด๊ฐ€๋Š” ๊ฐ’์€ ๊ฐ์ฒด์ด๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ์ฒด์•ˆ์˜ ๊ฐ’๋“ค์„ ๋‹ด๊ณ ์žˆ๋Š”Iinterface๋ฅผ Generic์œผ๋กœ ์ •ํ•ด์ฃผ์—ˆ๋‹ค

 

 

์•ก์…˜์˜ ๊ฐ์ฒด ํƒ€์ž… ๋งŒ๋“ค๊ธฐ

 

๋ฆฌ๋“€์„œ๋ฅผ ์ž‘์„ฑ ํ•  ๋•Œ action ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ํƒ€์ž…์„ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋ชจ๋“  ์•ก์…˜๋“ค์˜ TypeScript ํƒ€์ž…์„ ์ค€๋น„ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค

 

const actions = { submit, remove, toggle };
type TodoActions = ActionType<typeof actions>;

 

ActionType์„ ์‚ฌ์šฉํ•  ๋•Œ๋Š” Actions๋ผ๋Š” ๊ฐ์ฒด์— ๋ชจ๋“  ์•ก์…˜ ์ƒ์„ฑํ•จ์ˆ˜๋ฅผ ๋„ฃ์€ ๋‹ค์Œ์—, ActionType์œผ๋กœ ๊ฐ์‹ธ์ฃผ๋ฉด ๋œ๋‹ค

 

 

๋ฆฌ๋“€์„œ ๋งŒ๋“ค๊ธฐ

 

createReducer๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฆฌ๋“€์„œ๋ฅผ ์ƒ์„ฑํ•˜์ž createReducer์˜ ์‚ฌ์šฉ๋ฒ•์€ Redux-toolkit์˜ createReducer์˜ ์‚ฌ์šฉ๋ฒ•๊ณผ ๋™์ผํ•˜๋‹ค 

createReducer๋Š” Generic ์œผ๋กœ ์ƒํƒœ์˜ ํƒ€์ž…๊ณผ ์•ก์…˜๋“ค์˜ ํƒ€์ž…์„ ๋„ฃ์–ด์ฃผ์–ด์•ผ ํ•œ๋‹ค

 

 

const initialState: ToDosState = {  // initialState์— interface ToDosState๋ฅผ ํƒ€์ž…์œผ๋กœ ์ง€์ •
  todoItems: [],
};

const todo = createReducer<ToDosState, TodoActions>(initialState, {
// ๊ฐ์ฒด๋ฅผ ํŽ˜์ด๋กœ๋“œ๋กœ ๋ฐ›์•„ todoItems State์— ์ถ”๊ฐ€ํ•ด์ฃผ๋Š” SUBMIT ์•ก์…˜
  [SUBMIT]: (state, action) => ({
    ...state,
    todoItems: [...state.todoItems, action.payload],
  }),
  [REMOVE]: (state, action) => ({
// todoList์ค‘ ์„ ํƒ๋œ todo์˜ Id๋ฅผ ํŽ˜์ด๋กœ๋“œ๋กœ ๋ฐ›์•„ ์„ ํƒ๋œ todo๋ฅผ ์‚ญ์ œํ•ด์ฃผ๋Š” REMOVE ์•ก์…˜  
    ...state,
    todoItems: state.todoItems.filter(
      (todo: { id: number }) => todo.id !== action.payload
    ),
  }),
  [TOGGLE]: (state, action) => ({
// todoList์ค‘ ์„ ํƒ๋œ todo์˜ Id๋ฅผ ํŽ˜์ด๋กœ๋“œ๋กœ ๋ฐ›์•„ done ์†์„ฑ์„ ๋ณ€๊ฒฝํ•ด์ฃผ๋Š” TOGGLE ์•ก์…˜
    ...state,
    todoItems: state.todoItems.map((todo) =>
      todo.id === action.payload ? { ...todo, done: !todo.done } : todo
    ),
  }),
});

 

ActionType์œผ๋กœ ๊ฐ์‹ธ์ค€ TodoActions์™€ initialState๋ฅผ ์„ค์ •ํ•˜์—ฌ ๊ฐ๊ฐ์˜ ์•ก์…˜์„ ์ฒ˜๋ฆฌํ•œ๋‹ค

 

 

 

 

container ์ปดํฌ๋„ŒํŠธ๋“ค ์ƒ์„ฑํ•˜๊ธฐ 

 

container ์ปดํฌ๋„ŒํŠธ๋กœ์จ TodoContainer, TodoFormContainer, TodoListContainer ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ๊ฐ ์ƒ์„ฑํ•œ๋‹ค 

 

์šฐ์„  ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๋“ค์„ ์—ฐ๊ฒฐ์‹œ์ผœ์ค„ TodoContainer ๋จผ์ € ์ƒ์„ฑํ•ด๋ณด์ž

 

TodoContainer 

 

import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import Todo from '../components/todo/Todo';

interface TodoItem { 
  id: number;
  text: string;
  done: boolean;
}

interface ReduxState { // useSelector๋กœ ๋ฐ›์•„์˜จ State์˜ ์ƒํƒœ๋ฅผ ์ง€์ •ํ•ด์ฃผ๋Š” interface
  todoItems: TodoItem[];
}

function TodoContainer() {
  const [selected, setSelected] = useState('Doing'); 
  // ์„ ํƒ๋œ todo๊ฐ€ ์„ ํƒ๋˜์–ด completed ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•œ state

  const handleSelected = (e: React.MouseEvent<HTMLButtonElement>) => { 
  // ํด๋ฆญ๋œ todo์˜ event๊ฐ์ฒด๋ฅผ ๋ฐ›์•„์™€์„œ selected State๋ฅผ ๋ณ€๊ฒฝํ•ด์ฃผ๋Š” ํ•จ์ˆ˜
    setSelected(e.currentTarget.value);
  };

  const todos = useSelector((state: ReduxState) => state.todoItems);


  let doingTodo: TodoItem[] = todos.filter((todo) => todo.done === false); 
  let completedTodo: TodoItem[] = todos.filter((todo) => todo.done === true);
//doingTodo, completedTodo ๋ณ€์ˆ˜๋Š” ์ƒํƒœ๋กœ ๋ฐ›์•„์˜จ todos๋ฅผ ์†์„ฑ done์ด false์ผ๋•Œ, true์ผ๋•Œ๋ฅผ
//๊ตฌ๋ถ„ํ•˜์—ฌ ์ƒˆ๋กœ์šด ๋ฐฐ์—ด์„ ๋ฆฌํ„ดํ•˜์—ฌ ๋‹ด๊ณ ์žˆ๋Š” ๋ณ€์ˆ˜

  return (
    <Todo
      todos={todos}
      handleSelected={handleSelected}
      selected={selected}
      doingTodo={doingTodo}
      completedTodo={completedTodo}
    />
  );
}

export default TodoContainer;

 

์œ„์™€ ๊ฐ™์ด TodoContainer ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฆฌ๋•์Šค์˜ ์ƒํƒœ๋ฅผ ๋ฐ›๊ณ  ๋‚ด๋ถ€ State๋ฅผ ์ƒ์„ฑํ•˜์—ฌ Presentational ์ปดํฌ๋„ŒํŠธ์ธ Todo ์ปดํฌ๋„ŒํŠธ์— Props๋กœ ์ „๋‹ฌํ•ด์ค€๋‹ค

 

 

TodoFormContainer

๋‹ค์Œ์œผ๋กœ TodoForm ์ปดํฌ๋„ŒํŠธ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃจ๋Š” TodoFormContainer ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•ด๋ณด์ž 

 

 

import React, { useCallback } from 'react';
import TodoForm from '../components/todoForm/TodoForm';
import { useState } from 'react';
import { submit } from '../modules/todo'; // ์•ก์…˜ submit import
import { useDispatch } from 'react-redux';

function TodoFormContainer() {
  const [form, setForm] = useState('');
  const [id, setId] = useState(1);

  const dispatch = useDispatch(); //useDispatch hook์„ ์‚ฌ์šฉํ•˜์—ฌ dispatch ๋ณ€์ˆ˜์— ๋‹ด๋Š”๋‹ค

  const onSubmit = useCallback( 
  // ์‹คํ–‰๋˜์–ด์ง€๋ฉด ๋งค๊ฐœ๋ณ€์ˆ˜ text๋ฅผ ๋ฐ›์•„ ์ƒˆ๋กœ์šด ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜์—ฌ submit ์•ก์…˜์„ ๋””์ŠคํŒจ์น˜ ํ•ด์ฃผ๋Š” ํ•จ์ˆ˜์ด๋‹ค
    (text) => {
      dispatch(submit({ id: id, text: text, done: false }));
      setId(id + 1); // submit๋˜๋ฉด ๋‹ค์Œ id๊ฐ’ 1 ์ฆ๊ฐ€
    },
    [dispatch, id]
  );

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    onSubmit(form);
    setForm('');
  };

  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { value } = e.target;
    setForm(value);
  };

  return (
    <TodoForm
      form={form}
      handleSubmit={handleSubmit}
      onChange={onChange}
    ></TodoForm>
  );
}

export default TodoFormContainer;

 

TodoFormContainer ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” useDispatch๋ฅผ ์‚ฌ์šฉํ•ด ์•ก์…˜ submit์„ ์‹คํ–‰์‹œ์ผœ TodoList์— ์ถ”๊ฐ€ ๋  ๊ฐ์ฒด๋ฅผ ํŽ˜์ด๋กœ๋“œ๋กœ ๋ณด๋‚ด์ค€๋‹ค

 

 

TodoListContainer 

 

๋‹ค์Œ์œผ๋กœ TodoList ์ปดํฌ๋„ŒํŠธ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃจ๋Š” TodoListContainer ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•ด๋ณด์ž 

 

 

import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import TodoList from '../components/todoList/TodoList';
import { remove, toggle } from '../modules/todo';

interface TodoListContainerProps { 
// todo ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฐ›์€ Props์˜ ํƒ€์ž…์„ ์ง€์ •ํ•ด์ค„ interface
  todo: {
    id: number;
    text: string;
    done: boolean;
  };
}

function TodoListContainer({ todo }: TodoListContainerProps) {
  const dispatch = useDispatch(); //useDispatch hook ์‚ฌ์šฉํ•˜์—ฌ dispatch ๋ณ€์ˆ˜์— ๋‹ด๋Š”๋‹ค

  const onRemove = useCallback(
  // todo๋ผ๋Š” ์ด๋ฆ„์˜ ์„ ํƒ๋œ todo์˜ id๋ฅผ ๋‹ด์€ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ remove ์•ก์…˜์˜ ํŽ˜์ด๋กœ๋“œ๋กœ ๋„ฃ๋Š”๋‹ค
    (todo: number) => dispatch(remove(todo)),
    [dispatch]
  );

  const onToggle = useCallback(
  // todo๋ผ๋Š” ์ด๋ฆ„์˜ ์„ ํƒ๋œ todo์˜ id๋ฅผ ๋‹ด์€ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ toggle ์•ก์…˜์˜ ํŽ˜์ด๋กœ๋“œ๋กœ ๋„ฃ๋Š”๋‹ค
    (todo: number) => dispatch(toggle(todo)),
    [dispatch]
  );

  const handleDelete = () => {
    onRemove(todo.id);
  };

  const handleToggle = () => {
    onToggle(todo.id);
  };

  return (
    <TodoList
      todo={todo}
      handleDelete={handleDelete}
      handleToggle={handleToggle}
    />
  );
}

export default React.memo(TodoListContainer);

 

 

TodoListContainer ์—์„œ๋Š” useDispatch๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ remove, toggle ์•ก์…˜์„ ๋””์ŠคํŒจ์น˜ ํ•ด์ค€๋‹ค

 

 

Presentational ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑํ•˜๊ธฐ

 

 

Todo 

 

import React from 'react';
import TodoHeader from '../todoHeader/TodoHeader';
import styles from './Todo.module.css';
import TodoFormContainer from '../../containers/TodoFormContainer';
import TodoListContainer from '../../containers/TodoListContainer';

interface TodoItem {
  id: number;
  text: string;
  done: boolean;
}

interface TodoProps {
  todos: TodoItem[];
  handleSelected: (e: React.MouseEvent<HTMLButtonElement>) => void;
  selected: string;
  doingTodo: TodoItem[];
  completedTodo: TodoItem[];
}

function Todo({
  todos,
  handleSelected,
  selected,
  doingTodo,
  completedTodo,
}: TodoProps) {
  return (
    <div className={styles.Todo}>
      <header className={styles.header}>
        <TodoHeader />
      </header>
      <section className={styles.form}>
        <TodoFormContainer />
      </section>
      <div className={styles.buttons}>
      // ๊ฐ ๋ฒ„ํŠผ๋งˆ๋‹ค selected ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜์—ฌ ๋™์ผํ•˜๋ฉด class๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ๋„๋ก ํ•จ
        <button
          className={`${styles.button} ${
            selected === 'Doing' && styles.btnClicked
          }`}
          value="Doing"
          onClick={handleSelected}
        >
          Doing
        </button>
        <button
          className={`${styles.button} ${
            selected === 'Completed' && styles.btnClicked
          }`}
          value="Completed"
          onClick={handleSelected}
        >
          Completed
        </button>
        <button
          className={`${styles.button} ${
            selected === 'ViewAll' && styles.btnClicked
          }`}
          value="ViewAll"
          onClick={handleSelected}
        >
          View All
        </button>
      </div>

      <ul className={styles.list}>
      // selected ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜์—ฌ ์ง€์ •๋œ ๊ฐ’๊ณผ ์ผ์น˜ํ•˜๋ฉด ๊ทธ์—๋งž๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ณด์—ฌ์ฃผ๋„๋ก ํ•จ
        {selected === 'ViewAll' &&
          todos.map((todo) => <TodoListContainer todo={todo} key={todo.id} />)}
        {selected === 'Doing' &&
          doingTodo.map((todo) => (
            <TodoListContainer todo={todo} key={todo.id} />
          ))}
        {selected === 'Completed' &&
          completedTodo.map((todo) => (
            <TodoListContainer todo={todo} key={todo.id} />
          ))}
      </ul>
    </div>
  );
}

export default Todo;

 

Todo ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” selected ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜์—ฌ TodoContainer ์ปดํฌ๋„ŒํŠธ์—์„œ ๋„˜๊ฒจ์ค€ doingTodo, completedTodo ๋ฐฐ์—ด์„ map ์„ ํ†ตํ•ด ๋ณด์—ฌ์ค€๋‹ค 

 

 

TodoForm

 

 

import React from 'react';
import styles from './TodoForm.module.css';

interface FormProps {
// Props๋กœ ๋ฐ›์€ ํ•จ์ˆ˜์™€ State์˜ ํƒ€์ž… ์ง€์ •
  handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
  form: string;
}

function TodoForm({ handleSubmit, onChange, form }: FormProps) {
  return (
    <form onSubmit={handleSubmit} className={styles.form}>
      <input value={form} onChange={onChange} className={styles.input} />
      <button type="submit" className={styles.button}>
        ADD
      </button>
    </form>
  );
}

export default TodoForm;

 

 

TodoForm ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” TodoFormContainer ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฐ›์€ Props์˜ onChange, handleSubmit ํ•จ์ˆ˜๋กœ input์˜ ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰, TodoList ์˜ Todo๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ๋Š” ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰์‹œํ‚จ๋‹ค

 

 

TodoList

 

import React from 'react';
import styles from './TodoList.module.css';
interface TodoListProps {
  todo: {
    id: number;
    text: string;
    done: boolean;
  };
  handleDelete: (todo: any) => void;
  handleToggle: (todo: any) => void;
}

function TodoList({ todo, handleDelete, handleToggle }: TodoListProps) {
  return (
    <li className={styles.todo}>
      <span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
        {todo.text}
      </span>
      <div>
        <input
          type="checkbox"
          checked={todo.done}
          readOnly={true}
          onClick={handleToggle}
        />
        <button className={styles.delete} type="button" onClick={handleDelete}>
          ๐Ÿ—‘
        </button>
      </div>
    </li>
  );
}

export default TodoList;

 

 

TodoList ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” TodoListContainer ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฐ›์€ Props์˜ handleToggle, handleDeleteํ•จ์ˆ˜๋กœ TodoList๋ฅผ ์‚ญ์ œํ•˜๊ฑฐ๋‚˜, ์„ ํƒ๋œ Todo์˜ done์†์„ฑ์„ ๋ณ€๊ฒฝํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜๋„๋ก ํ•œ๋‹ค

 

 

TodoHeader

 

๋งˆ์ง€๋ง‰์œผ๋กœ TodoList์˜ Header ์ด๋‹ค

 

 

import React from 'react';
import styles from './TodoHeader.module.css';

function TodoHeader() {
  return (
    <div className={styles.container}>
      <header className={styles.header}>Todo-List</header>
      <span className={styles.description}>what is your next plan?</span>
    </div>
  );
}

export default TodoHeader;

 

์ด์ œ ์™„์„ฑ๋œ ๋ชจ์Šต์„ ์‚ดํŽด๋ณด์ž ๐Ÿ˜Š

 

 

 

 

์ด๋ ‡๊ฒŒ Redux์™€ TypeScript๋ฅผ ๊ฐ™์ด ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์•˜๋‹ค typesafe-actions๋กœ ๋ฆฌ๋•์Šค๋ฅผ ๋”์šฑ ํŽธํ•˜๊ฒŒ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ ๊ฐ™์•„ ๋”์šฑ ๊ณต๋ถ€ํ•  ํ•„์š”๋ฅผ ๋А๋‚€๋‹ค! 

 

reference

 

TypeScript ํ™˜๊ฒฝ์—์„œ Redux๋ฅผ ํ”„๋กœ์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•˜๊ธฐ

https://velog.io/@velopert/use-typescript-and-redux-like-a-pro#typesafe-actions%EB%A1%9C-%EB%A6%AC%EB%8D%95%EC%8A%A4-%EB%AA%A8%EB%93%88-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81%ED%95%98%EA%B8%B0

 

TypeScript ํ™˜๊ฒฝ์—์„œ Redux๋ฅผ ํ”„๋กœ์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•˜๊ธฐ

์ด๋ฒˆ์— ์ค€๋น„ํ•œ ํŠœํ† ๋ฆฌ์–ผ์—์„œ๋Š” TypeScript ํ™˜๊ฒฝ์—์„œ Redux๋ฅผ ํ”„๋กœ์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋‹ค๋ค„๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์™œ ์ œ๋ชฉ์ด "ํ”„๋กœ์ฒ˜๋Ÿผ"์ด๋ƒ! ์‚ฌ์‹ค์€ ์กฐ๊ธˆ ์ฃผ๊ด€์ ์ž…๋‹ˆ๋‹ค. ์ด ํŠœํ† ๋ฆฌ์–ผ์—์„œ๋Š” ์ง€๊ธˆ๊นŒ์ง€

velog.io

 

 

 

'TypeScript' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์‹œ์ž‘ํ•˜๊ธฐ  (0) 2021.06.12