๐Ÿชด JavaScript & TypeScript

[TS/React] JS/props-drilling ๋ฐฉ์‹์œผ๋กœ ์“ด To-Do List ๋ฅผ TypeScript๋กœ ๋ฐ”๊ฟ”๋ณด๊ธฐ : ๋‹ต์•ˆ์ฐธ๊ณ 

๋ จ๋”” 2024. 3. 10. 00:57
SMALL

0309 ๐Ÿ’Ž

๐Ÿช„ TypeScript ์‚ฌ์šฉํ•ด To-Do List ๋งŒ๋“ค๊ธฐ - props-drilling ๋ฐฉ์‹

ํŠœํ„ฐ๋‹˜์˜ ๋‹ต์•ˆ ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด์ž!

ํ•˜์ง€๋งŒ ๋ญ”๊ฐ€ ์ฝ”๋“œ์—์„œ ์ž˜๋ชป ๋œ ๊ฒƒ์ธ์ง€ ์—๋Ÿฌ๊ฐ€ ๋– ์žˆ์—ˆ๊ณ  ใ… ใ… 

๋‚ด๊ฐ€ ์ฝ”๋“œ ์ˆ˜์ •ํ•ด์„œ ํ•ด๊ฒฐํ•˜๊ธด ํ–ˆ๋‹ค.

๊ฐ•์˜์—์„œ์™€ ๋‹ฌ๋ฆฌ hook์„ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•ด์„œ ๊ฐ•์˜ ๋•Œ ์ฝ”๋“œ์™€ ๋‹ค๋ฅด๊ธฐ๋„ ํ–ˆ๋‹ค.

  • ๋‹ต์•ˆ ์ฝ”๋“œ์˜ to-do list ๋Š” ๊ฐ„์†Œํ™”๋œ ํˆฌ๋‘๋ฆฌ์ŠคํŠธ๋ผ todo๋Š” id, title, isDone(์™„๋ฃŒ์—ฌ๋ถ€) ์˜ key๋“ค๋กœ๋งŒ ๊ฐ„๋‹จํžˆ ๊ตฌ์„ฑ๋จ.
  • App.tsx > TodoList.tsx ์ปดํฌ๋„ŒํŠธ > TodoItem.tsx ์ปดํฌ๋„ŒํŠธ ๋ฐฉ์‹์œผ๋กœ ์†ํ•ด์žˆ๋‹ค.
  • TodoList๊ฐ€ ๊ฑฐ์˜ ๋ฉ”์ธ์ด๋‹ค.
    ์ด ์ปดํฌ๋„ŒํŠธ์—์„œ hook์„ importํ•ด ์‚ฌ์šฉํ•˜๋ฉฐ, TodoItem์œผ๋กœ ์ด hook์—์„œ ๋ฐ›์€ ๋ฆฌํ„ด๊ฐ’์„ props ํ˜•ํƒœ๋กœ ์ „๋‹ฌํ•ด์ค€๋‹ค
  • hook์€ TodoList.hooks.ts ์— ํ•˜๋‚˜ ์žˆ๋Š” useTodo( ) ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค

 

 

<TodoList.tsx> - ๋ฉ”์ธ

import React from "react";
import { TodoItem } from "./Todo";
import { useTodo } from "./TodoList.hooks";

export type Todo = {
    // ์—ฌ๊ธฐ์„œ ์ด๋ ‡๊ฒŒ Todo ์•ˆ id,title,isDone ํƒ€์ž… ์ง€์ •ํ•ด์คฌ๊ธฐ๋•Œ๋ฌธ์—, ๋‹ค๋ฅธ ๊ณณ์—์„œ๋„ ํƒ€์ž… ์“ธ ๋–„
    // id : string ์ด๋ผ๊ณ  ์ง์ ‘ ์จ์ค˜๋„ ๋˜์ง€๋งŒ, ์ด๊ฑธ ์‘์šฉํ•ด์„œ, id: Todo["id"] ๋ผ๊ณ ๋„ ์“ธ ์ˆ˜ ์žˆ๋‹ค
    // ์•„๋งˆ ์ด๋ ‡๊ฒŒ ์ฒ˜๋ฆฌํ•ด์ฃผ๋ฉด ์ข‹์€ ์ ์ด, ๋งŒ์•ฝ ์ด ํƒ€์ž…๋‚ด์—์„œ id: number๋กœ ๋ฐ”๊พธ๋ฉด ๋งž๊ฒŒ ์•Œ์•„์„œ
    // id:Todo["id"]๋„ id:number๊ฐ€ ๋˜๋Š” ๊ฒƒ
    id: string;
    title: string;
    isDone: boolean;
}; // ์ด ์˜ˆ์‹œ์—์„œ๋Š” todo๋‚ด์šฉ์œผ๋กœ title๋งŒ input์œผ๋กœ ๋„ฃ๋„๋ก ๋‹ค๋ฃธ (๋”ฐ๋กœcontent,date์—†์Œ)

export const TodoList: React.FC = () => {
    const {
        todos,
        title,
        handleTitleChange,
        handleAddTodo,
        handleDeleteTodo,
        handleToggleTodo,
    } = useTodo(); // ์ด hook์—์„œ todos, title ๋“ฑ ๋ฆฌํ„ดํ•ด์ฃผ๋„๋ก ๋˜์–ด์žˆ์Œ / ํ›…์œผ๋กœ ๊ฐ„์†Œํ™”ํ•ด์ฃผ๊ธฐ! state์„ ์–ธ๋„ ๋‹ค hook์—์„œ ์ฒ˜๋ฆฌ

    return (
        <div>
            <input value={title} onChange={handleTitleChange} type="text" />
            <button
                onClick={() => {
                    handleAddTodo(title);
                }}
            >
                Add Todo
            </button>
            <ul>
                {todos.map((todo) => (
                    <TodoItem
                        key={todo.id}
                        todo={todo}
                        onToggle={handleToggleTodo}
                        onDelete={handleDeleteTodo}
                    />
                ))}
            </ul>
        </div>
    );
};

 

 

<TodoList.hooks.ts> - hook์ด ์žˆ๋Š” tsํŒŒ์ผ

์ด hook, useTodo ์•ˆ์—์„œ todos, title ์˜ state๋„ ์„ ์–ธํ•˜๊ณ  (useState)
title input ์˜ onChange ํ•จ์ˆ˜์ธ handleTitleChange,
todo ๋ฅผ ์ถ”๊ฐ€, ์‚ญ์ œ, ์™„๋ฃŒ์—ฌ๋ถ€ํ† ๊ธ€ํ•˜๋Š” handleAddTodo, handleDeleteTodo, handleToggleTodo ํ•จ์ˆ˜๋ฅผ ๋ชจ๋‘ ์จ์คŒ.


๊ทธ๋ž˜์„œ ์ด๋Ÿฌํ•œ todos, title, handleTitleChange, handleAddTodo, handleToggleTodo, handleDeleteTodo
๋ฅผ ๋ชจ๋‘ ๋ฆฌํ„ด๊ฐ’์œผ๋กœ ๋ฐ˜ํ™˜ํ•ด์ฃผ๋Š” ๋ฐฉ์‹์ด๋‹ค.

import { useState } from "react";
import { Todo } from "./TodoList";

export const useTodo = () => {
    const [todos, setTodos] = useState<Todo[]>([]);

    // ์›๋ž˜ title state์™€, handleTitleChange์ด ์žˆ์—ˆ๋˜ ๊ฑฐ ๊ฐ™์Œ! (๋น ์ง„๋“ฏ?)
    // ์•„๋ž˜๋Š” ๋‚ด๊ฐ€ ์ž‘์„ฑ 
    const [title, setTitle] = useState("");
    const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        setTitle(e.target.value);
    };

    const handleAddTodo = (title: Todo["title"]) => {
        const newTodo: Todo = {
            id: crypto.randomUUID(),
            title,
            isDone: false,
        };
        setTodos((prev) => [...prev, newTodo]);
        setTitle("");
    };

    const handleToggleTodo = (id: Todo["id"]) => {
        setTodos((prev) =>
            prev.map((todo) =>
                todo.id === id ? { ...todo, isDone: !todo.isDone } : todo
            )
        );
    };

    const handleDeleteTodo = (id: Todo["id"]) => {
        setTodos((prev) => prev.filter((todo) => todo.id !== id));
    };

    return {
        // ์ด ํ›…์—์„œ ๋ฆฌํ„ดํ•ด์ฃผ๋Š” ๊ฐ’๋“ค
        todos,
        title,
        handleTitleChange,
        handleAddTodo,
        handleToggleTodo,
        handleDeleteTodo,
    };
};

 

 

<Todo.tsx> - to-do item 

  • todoitem ์˜ ๋‚ด์šฉ์ด ๋“ค์–ด์žˆ๋‹ค
  • ์ปดํฌ๋„ŒํŠธ ํƒ€์ž…์œผ๋กœ : React.Fc<ํ”„๋กญ์Šค ํƒ€์ž…> ์„ ์จ์ฃผ๋ฉด ๋จ. ์œ„์—์„œ TodoProps ํƒ€์ž…์„ ๋งŒ๋“ค์–ด๋‘์—ˆ์œผ๋‹ˆ, ๊ทธ๊ฑธ <>์•ˆ์— ๋„ฃ๊ธฐ - ( ์ œ๋„ค๋ฆญ < > )  (๋ฌผ๋ก  ์ด ๋ฐฉ์‹์„ ๊ผญ ์“ธ ํ•„์š˜์—†๋‹ค)
  • FC: Functional Component ์ค„์ž„๋ง
  • React.Fc ๋กœ ํƒ€์ž…์„ ์จ์ฃผ๋Š” ๊ฑด React 18๋ฒ„์ „ ์ด์ „ ๋ฐฉ์‹์ด๋ผ๊ณ  ๋“ค์—ˆ์œผ๋ฉฐ,  ์—ฌ๊ธฐ์— ์•”๋ฌต์ ์œผ๋กœ children์ด ํฌํ•จ๋˜์–ด์žˆ์–ด children props์—๋Œ€ํ•œ ํƒ€์ž… ์ฒดํ‚น์ด ๋ช…ํ™•ํ•˜์ง€ ์•Š๋‹ค๋Š” ๋ฌธ์ œ์ ์ด ์žˆ์–ด ์‚ฌ์šฉ์„ ์ง€์–‘ํ•˜์ž๋Š” ์–˜๊ธฐ๊ฐ€ ์žˆ์—ˆ๋‹ค๊ณ  ํ•œ๋‹ค.
    •  ๊ทผ๋ฐ ์ด ๋ถ€๋ถ„์ด ํ˜„์žฌ ์ข€ ์ˆ˜์ •์ด ๋œ๊ฑด์ง€? ํ•˜ํŠผ ๋‹ค์‹œ ์จ๋„ ๋œ๋‹ค๋Š” ์‹์œผ๋กœ ๋“ค์€ ๊ฑฐ ๊ฐ™๋‹ค. ํ™•์‹ค์น˜์•Š์Œ ..!
import React from "react";
import { Todo } from "./TodoList"; // ํƒ€์ž… ์ž„ํฌํŠธ

type TodoProps = {
    todo: Todo;
    onToggle: (id: Todo["id"]) => void; // (id: string) => .. ๊ณผ ๊ฐ™๋‹ค
    onDelete: (id: Todo["id"]) => void;
};

export const TodoItem: React.FC<TodoProps> = ({ todo, onToggle, onDelete }) => {
    return (
        <li>
            <input
                type="checkbox" // ์—ฌ๊ธฐ์„œ๋Š” ์™„๋ฃŒ/์ทจ์†Œ๋ฒ„ํŠผ์ด ์•„๋‹ˆ๋ผ, ์ฒดํฌ๋ฐ•์Šค [v] input์œผ๋กœ toggle ํ•ด์ฃผ๊ณ  ์žˆ์Œ
                checked={todo.isDone}
                onChange={() => onToggle(todo.id)} // ์ฆ‰, handleToggleTodo ํ•จ์ˆ˜. ์ธ์ž๊ฐ€ ์žˆ์œผ๋‹ˆ ์ฝœ๋ฐฑํ•จ์ˆ˜๋กœ ๋„ฃ๋Š”๋‹ค
            />
            {todo.title}
            <button onClick={() => onDelete(todo.id)}>Delete</button>
        </li>
    );
};
SMALL