TypeScript TodoList

์ด๋™์–ธยท2024๋…„ 9์›” 23์ผ

new world

๋ชฉ๋ก ๋ณด๊ธฐ
47/62
post-thumbnail

9.23 (์›”)

1. Input ์ด๋ฒคํŠธ

๐Ÿ‘‰ Vue์™€ React๋Š” ํฐ ์ฐจ์ด์ ์ด ์žˆ๋Š”๋ฐ, ๊ทธ ์ค‘ ํ•œ๊ฐ€์ง€๋Š” ์–‘๋ฐฉํ–ฅ๊ณผ ๋‹จ๋ฐฉํ–ฅ์ด๋‹ค.
๐Ÿ‘‰ Vue๊ฐ™์€ ๊ฒฝ์šฐ์—๋Š” Inputํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ, v-model์„ ์‚ฌ์šฉํ•˜์—ฌ input์— ์ž‘์„ฑ๋˜๋Š” ๋‚ด์šฉ์ด ์–‘๋ฐฉํ–ฅ์œผ๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ์ž‘์„ฑ์ด ๋˜์ง€๋งŒ, React์ธ ๊ฒฝ์šฐ์—๋Š” ๊ทธ๋ ‡์ง€์•Š๊ธฐ ๋•Œ๋ฌธ์— Input์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ด๋ฒคํŠธ๋ฅผ ๊ฑธ์–ด ์–‘๋ฐฉํ–ฅ์˜ ํ˜•ํƒœ๋กœ ์‚ฌ์šฉํ•˜๊ฒŒ๋œ๋‹ค.

1-1. TodoInput.tsx

import {ITodo} from "../../type/todo.ts";
import {ChangeEvent, useState} from "react";

const initState: ITodo = {
    title : '',
    writer : '',
    dueDate : '',
}

function TodoInput() {

    const [todo, setTodo] = useState<ITodo>(initState) // ๊ธฐ๋ณธ๊ฐ’์„ initalState

    const handleChange =  (e:ChangeEvent<HTMLInputElement>) => { // ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๊ฐ’์— ๋Œ€ํ•œ ์ด๋ฒคํŠธ
        console.log(e)
        console.log(e.target)
        //console.log(e.target.value);
        //console.log(e.target.name);


        todo[e.target.name] = e.target.value

        setTodo({...todo})
    }

    return (
        <div className="flex flex-col space-y-4 w-96 mx-auto">
            <label htmlFor="title" className="text-sm font-semibold text-gray-700">Title</label>
            <input
                type="text"
                name="title"
                className="border border-gray-300 rounded-lg p-3 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition duration-300"
                placeholder="Enter Title"
                value={todo.title}
                onChange={e => handleChange(e)}
            />

            <label htmlFor="writer" className="text-sm font-semibold text-gray-700">Writer</label>
            <input
                type="text"
                name="writer"
                className="border border-gray-300 rounded-lg p-3 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition duration-300"
                placeholder="Enter Title"
                value={todo.writer}
                onChange={e => handleChange(e)}
            />

            <label htmlFor="dueDate" className="text-sm font-semibold text-gray-700">DueDate</label>
            <input
                type="date"
                name="dueDate"
                className="border border-gray-300 rounded-lg p-3 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition duration-300"
                placeholder="Enter Title"
                value={todo.dueDate}
                onChange={e => handleChange(e)}
            />
        </div>
    );
}

export default TodoInput;

๐Ÿ‘‰ ํ•ด๋‹น์ฝ”๋“œ์—์„œ handleChange๋ฅผ ๋ณด๊ฒŒ ๋˜๋ฉด e(์ด๋ฒคํŠธ)๊ฐ€ ์žˆ๋Š”๋ฐ, ํ•ด๋‹น e์—๋Š” ๋‹ค์–‘ํ•œ ์†์„ฑ์ค‘์— target์†์„ฑ์ด ์žˆ๊ณ , ๊ทธ๋‚ด๋ถ€์—๋Š” name ์†์„ฑ์ด ์žˆ๋‹ค.
๐Ÿ‘‰ ์•„๋ž˜ return์˜ input ํƒœ๊ทธ ๋‚ด๋ถ€์˜ name๊ฐ’์ด title์ด๋ฏ€๋กœ target์†์„ฑ์˜ name์†์„ฑ๋˜ํ•œ title์ด ๋œ๋‹ค. ํ•˜์—ฌ, ํ•ด๋‹น ์ž…๋ ฅ์ค‘์ธ input๊ฐ’์ด title์ด๋ผ๋ฉด

todo[e.target.name] = e.target.value
todo[title] = value(์ž…๋ ฅ๋˜๋Š”๊ฐ’) ์ด ๋˜๋Š”๊ฒƒ์ด๋‹ค.




2. React ์ฝ”๋“œ๋ถ„์„

2-0. todo.ts

export interface ITodo{
    mno? : number,
    title : string,
    writer : string,
    dueDate : string,
}

export interface IPageResponse{
    content : ITodo[],
    totalElements : number,
    number : number,
    first : boolean,
    last : boolean,
    size : number,
    totalPages: number,
}

๐Ÿ‘‰ typeScript๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ type์„ ๋ฏธ๋ฆฌ ๊ตฌ์„ฑํ•ด๋‘๊ณ  ์‹œ์ž‘ํ•˜๋Š”๋ฐ, ํ•ด๋‹น ํƒ€์ž…์€ IpageResponse{ITodo[], totalElements..} ์˜ ํ˜•ํƒœ์ด๋‹ค.




2-1. TodoAPI.ts

import {IPageResponse, ITodo} from "../type/todo.ts";
import axios from "axios";


const host:string = 'http://localhost:8088/api/v1/todos'

export const getTodoList = async (page?:number, size?:number): Promise<IPageResponse> => {

    const pageValue:number = page || 1 // ์—†์œผ๋ฉด 1ํŽ˜์ด์ง€
    const sizeValue:number = size || 10 // ์—†์œผ๋ฉด size๋Š” 10

    const res = await axios.get(`${host}/list?page=${pageValue}&size=${sizeValue}`)

    return res.data;

}

export const postTodo = async (todo:ITodo): Promise<number> => { // mno๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ์œ„ํ•ด return๊ฐ’์„ number

    const res = await axios.post(`${host}`, todo) // ํ•ด๋‹น url์„ ํ†ตํ•ด todo ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์„œ๋ฒ„์— ์ „๋‹ฌ

    return res.data.mno
}

๐Ÿ‘‰ ํ•ด๋‹น์ฝ”๋“œ๋Š” api์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›๋Š” ์ฝ”๋“œ๋กœ docker์„œ๋ฒ„๋ฅผ host๋กœ ์žก์•„๋‘๊ณ ,
getTodoList๋ฅผ ํ†ตํ•ด ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ page์™€ size๋กœ ํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ getํ•˜์—ฌ return๊ฐ’์œผ๋กœ๋Š” IPageResponse์˜ ํ˜•ํƒœ๋กœ ๋ฐ›์•„์˜จ๋‹ค.

๐Ÿ‘‰ post๋ฅผ ํ†ตํ•ด api์„œ๋ฒ„์— todo์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ ๊ฐ’์„ ๋„ฃ์–ด์ฃผ๊ณ  return๊ฐ’์œผ๋กœ mno์ธ number๊ฐ’์€ ๋ฐ›์•„์˜จ๋‹ค. - ์ด ๊ฐ’์€ ๋‚˜์ค‘์— modal์ฐฝ์—์„œ "๋ช‡ ๋ฒˆ์ด ๋“ฑ๋ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค" ์— ์‚ฌ์šฉ๋œ๋‹ค.




2-2. LoadingComponent.tsx

function LoadingComponent() {
    return (
        <div className="fixed inset-0 flex items-center justify-center bg-gray-900 bg-opacity-50 z-50">
            <div className="bg-white p-8 rounded-lg shadow-lg w-96">
                <h2 className="text-xl font-semibold mb-4">Loading</h2>
                <p className="mb-6">Loading</p>
            </div>
        </div>
    );
}

export default LoadingComponent;

๐Ÿ‘‰ ํ•ด๋‹น ์ฝ”๋“œ๋Š” input ํŽ˜์ด์ง€ ๋‚ด๋ถ€์—์„œ ๋“ฑ๋ก์ด ๋œ ์ดํ›„์— ์ž ์‹œ์˜ ๋กœ๋”ฉํŽ˜์ด์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด commonํŒจํ‚ค์ง€์— ๊ตฌ์„ฑํ•˜์˜€๋‹ค.




2-3. PageComponent.tsx

import {IPageResponse} from "../../type/todo.ts";

interface Props {
    pageResponse:IPageResponse;
    changePage:(p:number) => void;
}

const makeArr = (from:number, to:number, prev:boolean, next:boolean):number[] => {

    const arr:number[] = []

    if(prev){
        arr.push(from-1)
    }

    for (let i = from; i < to; i++) {
        arr.push(i);
    }
    return arr
}


function PageComponent({pageResponse, changePage}:Props) {

    const current: number = pageResponse.number + 1 // ํ˜„์žฌํŽ˜์ด์ง€ 0 ๋ถ€ํ„ฐ์‹œ์ž‘ํ•˜๋ฏ€๋กœ + 1
    const tempLast: number = Math.ceil(current/10.0) * 10 // ํ˜„์žฌ 11ํŽ˜์ด์ง€์ผ๋•Œ 20ํŽ˜์ด์ง€๊นŒ์ง€ ๋‚˜์˜ค๊ธฐ
    const startPage: number = tempLast - 9
    const endPage: number = pageResponse.totalPages < tempLast ? pageResponse.totalPages : tempLast;
    //const prev: boolean = startPage !== 1

    const pageNums:number[] = makeArr(startPage, endPage, false, false)

    const lis = pageNums.map( num => <li
        className='px-4 py-2 text-white bg-blue-500 border border-blue-500 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-300'
        key={num}
        onClick={() =>changePage(num)}
    >

        {num}

    </li>)

    return (
        <div>
            <ul className='flex justify-center items-center space-x-2 mt-6'>
                {lis}
            </ul>
        </div>
    );
}

export default PageComponent;

๐Ÿ‘‰ ํ•ด๋‹น์ฝ”๋“œ๋Š” page์˜ ๋ฒ„ํŠผ ์—ญํ• ์„ ํ•˜๋Š” ๊ณตํ†ต ์ฝ”๋“œ์ธ๋ฐ, PageComponent์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ ๊ฐ’์œผ๋กœ pageResponse, changePage๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ์œ„ํ•ด interface๋กœ ์ •์˜ํ•˜์˜€๊ณ , ์ด๋Š” TodoList์—์„œ ์‚ฌ์šฉ๋  ์˜ˆ์ •์ด๋‹ค.




2-4. ResultModal.tsx

interface ResultModalProps {
    msg: string;
    callback:() => void
}


function ResultModal({ msg, callback } :ResultModalProps) {
    return (
        <div className="fixed inset-0 flex items-center justify-center bg-gray-900 bg-opacity-50 z-50">
            <div className="bg-orange-500-600 p-8 rounded-lg shadow-lg w-96">
                <h2 className="text-xl font-semibold mb-4">Result</h2>
                <p className="mb-6">{msg}</p>
                <button className='bg-orange-500 text-white px-4 py-2 rounded hover:bg-orange-500'
                        onClick={() => callback()}
                >
                    cancel
                </button>
            </div>
        </div>
    );
}

export default ResultModal;

๐Ÿ‘‰ ํ•ด๋‹น์ฝ”๋“œ๋Š” input ํŽ˜์ด์ง€์—์„œ ๋“ฑ๋ก๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„๋•Œ, ๋ช‡๋ฒˆ์ด ๋“ฑ๋ก์ด๋˜๊ณ , callback์„ ํ•˜์—ฌ input ๋‚ด๋ถ€์˜ ์ฝ”๋“œ๋ฅผ ์ดˆ๊ธฐํ™” ์‹œ์ผœ์ฃผ๋Š” ์ฝ”๋“œ์ด๋‹ค.



2-5. TodoIndex.tsx

import TodoList from "./TodoList.tsx";
import {ReactElement, useState} from "react";
import TodoInput from "./TodoInput.tsx";

function TodoIndex(): ReactElement {

    const [page, setPage] = useState(1);
    const [refresh, setRefresh] = useState(false);
    const changePage = (pageNum: number) => {
        setPage(pageNum);
        setRefresh(!refresh);
    }

    return (
        <div className='flex flex-col'>
            <TodoInput changePage={changePage}></TodoInput>
            <TodoList pageNum={page} refresh={refresh} changePage={changePage}></TodoList>
        </div>
    );
}

export default TodoIndex;

๐Ÿ‘‰ ํ•ด๋‹น ์ฝ”๋“œ๋Š” TodoInput, TodoList์˜ ๋ถ€๋ชจ์ปดํฌ๋„ŒํŠธ์˜ ์—ญํ• ์„ํ•˜๊ณ , page์™€ ๊ด€๋ จ๋œ page, refresh๋ฅผ list๊ฐ€ ์•„๋‹Œ ๋ถ€๋ชจ์ปดํฌ๋„ŒํŠธ์— ๋นผ๋’€๋Š”๋ฐ, ์ด ์ด์œ ๊ฐ€ ๋งค์šฐ ์ค‘์š”ํ•˜๋‹ค.

๐Ÿ“Œ ๊ธฐ๋ณธ์ ์œผ๋กœ TodoList์—์„œ ํŽ˜์ด์ง€ ๊ตฌํ˜„์„ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํŽ˜์ด์ง€์™€ ๊ด€๋ จ๋œ ์†Œ์Šค์ฝ”๋“œ๋“ค์„ Listํƒœ๊ทธ๋‚ด๋ถ€๋กœ ๋„ฃ๊ฒŒ ๋˜๋Š”๋ฐ, ์ด๋ ‡๊ฒŒ ๋˜๋ฉด ํŽ˜์ด์ง€์™€ ๊ด€๋ จ๋œ ๊ธฐ๋Šฅ์„ List์—์„œ ๋ฐ–์— ์‚ฌ์šฉํ•˜์ง€ ๋ชปํ•œ๋‹ค. ๊ณ ๋กœ, Input ํƒœ๊ทธ๋‚ด์—์„œ๋Š” ํŽ˜์ด์ง€ ์ด๋™์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

๐Ÿ“Œ Input์—์„œ๋Š” ํŽ˜์ด์ง€ ์ด๋™์ด ํ•„์š”์—†๋‹ค๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๋“ฑ๋ก๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๊ณ ๋‚˜์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ 1ํŽ˜์ด์ง€๋กœ ๊ฐ€๋Š”๊ฒƒ์ด ์ผ๋ฐ˜์ ์ด๋‹ค. ๊ทธ๋Ÿฌ๋ฏ€๋กœ ํ•ด๋‹น ๊ธฐ๋Šฅ๊ณผ ๊ฐ™์ด ์–ด๋””์ชฝ์— ๊ธฐ๋Šฅ์„ ๋ถ€์—ฌํ•˜๋Š”๊ฒƒ์ด ์• ๋งคํ•˜๋‹ค๋ฉด ๋ถ€๋ชจ์ชฝ์œผ๋กœ ๋ถ€์—ฌํ•˜๋Š”๊ฒƒ์ด ํƒ€๋‹นํ•˜๋‹ค.

๐Ÿ‘‰ return๋‚ด๋ถ€์—์„œ๋Š” TodoInput์—์„œ๋Š” 1ํŽ˜์ด์ง€๋กœ ๊ฐ€๋Š” ๊ธฐ๋Šฅ์ด ํ•„์š”ํ•˜๋ฏ€๋กœ changePage์˜ ๊ธฐ๋Šฅ์„ ๋ถ€์—ฌํ•˜๊ณ , ListํŽ˜์ด์ง€์—์„œ๋Š” page์™€ ๊ด€๋ จ๋œ ๋ชจ๋“  ๊ธฐ๋Šฅ๋“ค์„ ๋ถ€์—ฌํ•œ๋‹ค. - ์ด๋ ‡๊ฒŒ ๋ถ€์—ฌํ•˜๋Š”๊ฒƒ์„ props์˜ ํ˜•ํƒœ๋กœ ๋ถ€์—ฌํ•˜๋Š”๊ฒƒ์ด๋‹ค.




2-6. TodoInput.tsx

import {ITodo} from "../../type/todo.ts";
import React, {ChangeEvent, useState} from "react";
import {postTodo} from "../../api/TodoAPI.ts";
import LoadingComponent from "../common/LoadingComponent.tsx";
import ResultModal from "../common/ResultModal.tsx";

const initState: ITodo = {
    title : '',
    writer : '',
    dueDate : '',
}

interface TodoInputProps { // input ๋ฒ„ํŠผ ์ดํ›„์— 1๋ฒˆํŽ˜์ด์ง€ ์ด๋™ํ•˜๊ธฐ ์œ„ํ•ด์„œ
    changePage: (p:number) => void
}

function TodoInput({changePage}:TodoInputProps) {

    const [todo, setTodo] = useState<ITodo>({...initState}) // ๊ธฐ๋ณธ๊ฐ’์„ initalState
    const [loading, setLoading] = useState(false);
    const [resultData, setResultData] = useState<number>(0); // api์— post๋ฅผ ํ†ตํ•ด ๋“ฑ๋ก๋˜๊ณ  return๋˜๋Š” mno๊ฐ’์„ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•œ ์ƒํƒœ

    const handleChange=  (e:ChangeEvent<HTMLInputElement>) => { // ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๊ฐ’์— ๋Œ€ํ•œ ์ด๋ฒคํŠธ
        //console.log(e)
        //console.log(e.target)
        console.log(e.target.value);
        console.log(e.target.name);


        todo[e.target.name] = e.target.value

        setTodo({...todo})
    }

    const handleClick = () => {
        setLoading(true)

        postTodo(todo).then(number => {
            setLoading(false)
            setResultData(number) // ๋“ฑ๋ก์™„๋ฃŒ๋˜๊ณ  return๋˜๋Š” mno๊ฐ’์„ ResultData์— ์ฃผ์ž…
        })
    }

    const clearResult=() => {
        setResultData(0) // resultData ๊ฐ’์„ ์ดˆ๊ธฐํ™”
        setTodo(initState) // input ๋‚ด์šฉ ์ดˆ๊ธฐํ™”
        changePage(1) // 1๋ฒˆํŽ˜์ด์ง€๋กœ ์ด๋™
    }

    return (
        <div className="flex flex-col space-y-4 w-96 mx-auto">

            {loading && <LoadingComponent></LoadingComponent>}

            {resultData !== 0 && <ResultModal msg={`${resultData}๋ฒˆ ๋“ฑ๋ก์™„๋ฃŒ`} callback={clearResult}/> }

            <label className="text-sm font-semibold text-gray-700">Title</label>
            <input
                type="text"
                name="title"
                className="border border-gray-300 rounded-lg p-3 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition duration-300"
                placeholder="Enter Title"
                value={todo.title}
                onChange={e => handleChange(e)}
            />

            <label className="text-sm font-semibold text-gray-700">Writer</label>
            <input
                type="text"
                name="writer"
                className="border border-gray-300 rounded-lg p-3 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition duration-300"
                placeholder="Enter Title"
                value={todo.writer}
                onChange={e => handleChange(e)}
            />

            <label  className="text-sm font-semibold text-gray-700">DueDate</label>
            <input
                type="date"
                name="dueDate"
                className="border border-gray-300 rounded-lg p-3 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition duration-300"
                placeholder="Enter Title"
                value={todo.dueDate}
                onChange={e => handleChange(e)}
            />
            <button
                type="submit"
                className="bg-blue-500 text-white font-semibold py-3 px-6 rounded-lg shadow-lg hover:bg-blue-600 focus:outline-none focus:ring-4 focus:ring-blue-300 transition duration-300"
                onClick={handleClick}
            >
                Submit
            </button>
        </div>
    );
}

export default TodoInput;

๐Ÿ‘‰ ํ•ด๋‹น์ฝ”๋“œ๋Š” inputํŽ˜์ด์ง€๋กœ input ํƒœ๊ทธ์— ๋“ค์–ด๊ฐ€์•ผํ•  ๋‚ด์šฉ์€ iniState๋กœ ๋นˆ ๊ฐ์ฒด์˜ ํ˜•ํƒœ๋กœ ๊ตฌํ˜„ํ•ด๋†“๋Š”๋‹ค. ์ด๋ ‡๊ฒŒ ํ•ด๋†“์œผ๋ฉด ๋‚˜์ค‘์— callbackํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด์ „์— ์ž‘์„ฑํ–ˆ๋˜ ๋‚ด์šฉ์„ ์ดˆ๊ธฐํ™” ํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ‘‰ TodoInput ๋‚ด๋ถ€์˜ useState๊ฐ€ 3๊ฐœ์žˆ๋Š”๋ฐ, ๊ฐ๊ฐ์„ ์‚ดํŽด๋ณด์ž๋ฉด todo๋Š” ์šฐ์„  input ํƒœ๊ทธ์— ์ž…๋ ฅํ•œ ITodo๊ฐ์ฒด์˜ ํ˜•ํƒœ๋กœ ์ €์žฅ๋˜๊ณ , APIPost๋ฅผ ํ†ตํ•ด ์„œ๋ฒ„ํ†ต์‹ ์„ ํ•˜๋Š”๊ฒƒ์ด๋‹ค.
loading์€ ๊ธฐ๋ณธ false๋กœ ๋˜์–ด์žˆ๋Š”๋ฐ, loading(true)์ผ๋•Œ Loadingcomponent๊ฐ€ ์ ์šฉ๋˜์–ด ๋กœ๋”ฉํ™”๋ฉด์ด ์ž ์‹œ ์ถœ๋ ฅ์ด ๋˜๋Š” ์—ญํ• ์ด๊ณ , resultData๋Š” API์˜ POST๋ฅผ ํ†ตํ•ด INPUT์˜ ๋‚ด์šฉ์ด ์ €์žฅ์ด ๋˜์—ˆ๊ณ , return๊ฐ’์œผ๋กœ mno์ธ number๊ฐ’์„ ๋ฐ›๊ฒŒ๋˜๋Š”๋ฐ ์ด number๊ฐ’์„ ์ €์žฅํ•˜๊ฒŒ๋  ์ƒํƒœ๊ฐ€ resultData์ด๋‹ค. resultData๋ฅผ ํ†ตํ•ด์„œ modal์ฐฝ์—์„œ ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ๋‹ค.




2-7. TodoList.tsx

import React, {ReactElement, useEffect, useState} from "react";
import {IPageResponse, ITodo} from "../../type/todo.ts";
import {getTodoList} from "../../api/TodoAPI.ts";
import LoadingComponent from "../common/LoadingComponent.tsx";
import PageComponent from "../common/PageComponent.tsx";

const initalState: IPageResponse = {
    content : [],
    first: false,
    last: false,
    number: 0,
    size: 0,
    totalElements: 0,
    totalPages: 0
}

interface TodoListProps{
    pageNum: number;
    refresh: boolean;
    changePage: (p:number) => void
}

function TodoList({pageNum, refresh, changePage} : TodoListProps): ReactElement {


    const [pageData, setPageData] = useState<IPageResponse>(initalState);
    const [loading, setLoading] = useState(false);

    useEffect(() => {

        setLoading(true);

        getTodoList(pageNum).then(data => {
            setPageData(data)
            setTimeout(()=> {
                setLoading(false) // ๋ฐ์ดํ„ฐ ๋ฐ›๊ณ  ๋กœ๋”ฉ์ฐฝ๊บผ์ง
            }, 600)
        })

    },[pageNum, refresh]);

    const TodoLI = pageData?.content?.map((todo:ITodo)=>{
        return (
            <li key={todo.mno}>
                {todo.mno} -
                {todo.title} -
                {todo.writer} -
                {todo.dueDate}
            </li>
        )
    })



    return (
        <div>
            {loading && <LoadingComponent></LoadingComponent>}
            <ul>
                {TodoLI}
            </ul>
            <div>
                <PageComponent pageResponse={pageData} changePage={changePage}></PageComponent>
            </div>
        </div>
    );
}

export default TodoList;

๐Ÿ‘‰ useState์˜ ์ฒซ๋ฒˆ์งธ๋Š” pageData๋กœ api์˜ get์„ ํ†ตํ•ด ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€ setPageData์— ํ•ด๋‹น data๊ฐ’์„ ๋„ฃ์–ด pageData๋กœ ์ถœ๋ ฅํ•œ๋‹ค.

๐Ÿ‘‰ useEffect์˜ ์—ญํ• ์€ ์กฐ๊ฑด์ด ๋ณ€ํ• ๋•Œ๋งŒ ๋žœ๋”๋ง์„ ํ•˜๋Š”๊ฒƒ์ธ๋ฐ, pageNum, refesh๊ฐ์ฒด ์ฒ˜๋Ÿผ ๋‘๊ฐœ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋ณ€๊ฒฝ์ด ๋˜๋ฉด ํ•ด๋‹น ๋ฉ”์†Œ๋“œ๊ฐ€ ์‹คํ–‰๋˜๋Š” ๊ฒƒ.




3. useEffect

๐Ÿ‘‰ ์กฐ๊ฑด์ด ๋ณ€ํ• ๋•Œ๋งŒ ๋žœ๋”๋ง(ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ๊ฐ€ ๋ณ€๊ฒฝ๋ ๋•Œ๋งŒ)

busy waiting
๐Ÿ‘‰ ๋‚˜์œ์˜ˆ๋กœ busy waiting์„ ์„ค๋ช…ํ•˜์ž๋ฉด, ํ•ด๋‹น ๋ฐ์ดํ„ฐ๊ฐ€ ์™”๋Š”์ง€ ์•ˆ์™”๋Š”์ง€ ๊ณ„์† ์ฒดํฌ ํ•˜๊ธฐ์œ„ํ•ด while๋ฌธ์„ ๋„ฃ์–ด์„œ ๋น„๋™๊ธฐ ์ฝ”๋“œ๊ฐ€ ๋„์ฐฉํ•˜๋ฉด ๋žœ๋”๋ง์ด ๋˜๋„๋ก ๋ฌดํ•œ๋ฃจํ”„๋ฅผ ๋„๋Š”๊ฒƒ์ด๋‹ค.

๐Ÿ‘‰ ์ด๋Ÿฌํ•œ ๋ฌด์‹ํ•œ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ ์œ„ํ•ด์„œ useEffect๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

useEffect(() => {

        setLoading(true);

        getTodoList(pageNum).then(data => {
            setPageData(data)
            setTimeout(()=> {
                setLoading(false) // ๋ฐ์ดํ„ฐ ๋ฐ›๊ณ  ๋กœ๋”ฉ์ฐฝ๊บผ์ง
            }, 600)
        })

    },[pageNum, refresh]);

๐Ÿ‘‰ pageNum || refresh๊ฐ์ฒด๊ฐ€ ๋ณ€ํ•˜๊ฒŒ ๋˜๋ฉด ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋จ




4. useCallBack

๐Ÿ‘‰ ํŽ˜์ด์ง€๋ฅผ ์‹คํ–‰ํ• ๋•Œ๋งˆ๋‹ค ๋ฉ”์†Œ๋“œ๋ฅผ ๊ณ„์† ์‹คํ–‰ํ•ด์•ผํ•˜๋Š”๊ฒƒ์„ ๋ง‰๊ธฐ์œ„ํ•จ.

const clearResult=() => {
        setResultData(0) // resultData ๊ฐ’์„ ์ดˆ๊ธฐํ™”
        setTodo(initState) // input ๋‚ด์šฉ ์ดˆ๊ธฐํ™”
        changePage(1) // 1๋ฒˆํŽ˜์ด์ง€๋กœ ์ด๋™
    }


{resultData !== 0 && <ResultModal msg={`${resultData}๋ฒˆ ๋“ฑ๋ก์™„๋ฃŒ`} callback={clearResult}/> }

๐Ÿ‘‰ ๋“ฑ๋ก๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๊ณ  ์ด์ „์— ์‚ฌ์šฉํ–ˆ๋˜ ๋‚ด์šฉ์„ ์ž๋™์œผ๋กœ ์ดˆ๊ธฐํ™” ์‹œํ‚ค๋Š” ๊ธฐ๋Šฅ์„ ์œ„ํ•จ.




5. useMemo

๐Ÿ‘‰ ์žฅ๋ฐ”๊ตฌ๋‹ˆ์˜ ๋‚ด์šฉ์ด ๋ณ€๊ฒฝ๋˜์—ˆ์„๋•Œ ์ดํ•ฉ์„ ๋‹ค์‹œ ๊ณ„์‚ฐํ•ด์•ผํ•œ๋‹ค๋ฉด ์‚ฌ์šฉ๋˜๋Š” ์ฝ”๋“œ

function MyComponent({ items }) {
    const expensiveCalculation = (data) => {
        // ๋ณต์žกํ•œ ๊ณ„์‚ฐ
        return data.reduce((acc, item) => acc + item.value, 0);
    };

    const memoizedValue = useMemo(() => expensiveCalculation(items), [items]);

    return <div>{memoizedValue}</div>;
}




6. useRef

function TodoInput() {

    const selectRef = useRef();

    const handleClick = () => {

        console.log(selectRef.current)

    }

    return (
        <div>
            <select ref={selectRef}>
                <option value='1'>1</option>
                <option value='2'>2</option>
                <option value='3'>3</option>
                <option value='4'>4</option>
                <option value='5'>5</option>
            </select>

            <button onClick={handleClick}>GET</button>
        </div>
    );
}

๐Ÿ‘‰ useRef๋ฅผ ํ†ตํ•ด์„œ select๋ฒ„ํŠผ์„ ๋งŒ๋“ค์–ด ๋†“๊ณ 

import TodoList from "./TodoList.jsx";
import TodoInput from "./TodoInput.jsx";

function TodoIndex() {
    return (
        <div>
            <div className='text-4xl'>Todo Index Component</div>

            <TodoInput></TodoInput>

            <TodoList></TodoList>

            <TodoInput></TodoInput>

        </div>
    );
}

export default TodoIndex;

๐Ÿ‘‰ Index์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” Input์„ ๋‘๋ฒˆ ํ˜ธ์ถœํ•œ๋‹ค. ์ด๋•Œ select๋Š” ํ•˜๋‚˜์˜ ๋ฉ”์†Œ๋“œ์ง€๋งŒ ํ˜ธ์ถœ์‹œ์— name, id์™€ ๊ฐ™์€ ๊ตฌ๋ณ„ํ• ์ˆ˜์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์—†๋Š” ์ƒํƒœ์ด์ง€๋งŒ useRef๋ฅผ ํ†ตํ•ด์„œ ๊ฐ๊ฐ์˜ ๋ณ„๋„์˜ ๊ฐ’์„ ์–ป์–ด๋‚ผ ์ˆ˜ ์žˆ๋‹ค.




7. useContext

๐Ÿ‘‰ vue์—์„œ์˜ provide inject์™€ ๊ฐ™์ด ์ปดํฌ๋„ŒํŠธ๊ฐ„์— ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•ด์ฃผ๋Š” ๊ธฐ๋Šฅ

7-1. CountButtons.jsx

import {useContext} from "react";
import {CountContext} from "./CountIndex.jsx";

function CountButtons() {

    const {changeCount} = useContext(CountContext);

    return (
        <div>
            <button onClick={changeCount}>PLUS</button>
        </div>
    );
}

export default CountButtons;

7-2. CountDisplay.jsx

import {useContext} from "react";
import {CountContext} from "./CountIndex.jsx";

function CountDisplay() {

    const {count} = useContext(CountContext);


    return (
        <div className="text-4xl">
            {count}
        </div>
    );
}

export default CountDisplay;

7-3. CountIndex.jsx

import CountButtons from "./CountButtons.jsx";
import {createContext, useState} from "react";
import CountDisplay from "./CountDisplay.jsx";

export const CountContext = createContext() // context์ƒ์„ฑํ•˜์—ฌ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ์—์„œ CountContext๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ์‚ฌ์šฉ๋œ๋‹ค.

function CountIndex() {

    const [count, setCount] = useState(0);

    const changeCount = () => {
        setCount(count+1);
    }


    return ( // Provider๋ฅผ ํ†ตํ•ด์„œ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋กœ ์ „๋‹ฌํ•œ๋‹ค.
        <CountContext.Provider value={{count, changeCount}}>
            <CountDisplay></CountDisplay>
            <CountButtons></CountButtons>
        </CountContext.Provider>

    );
}

export default CountIndex;

๐Ÿ‘‰ createContext๋ฅผ ํ†ตํ•ด useContext๋ฅผ ์ƒ์„ฑํ•˜๊ณ , return๊ฐ’์˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ• ๋‹นํ•˜๋Š”๊ณณ์—์„œ CountContext.Provider์˜ ํ˜•ํƒœ์˜ ํƒœ๊ทธ๋กœ ํ• ๋‹นํ•  ์ปดํฌ๋„ŒํŠธ๋“ค์„ ๊ฐ์‹ผ๋‹ค.

๐Ÿ‘‰ ์ด๋ ‡๊ฒŒ ๋˜๋ฉด count๋ณ€์ˆ˜, changeCount๋ฉ”์†Œ๋“œ๋“ค์„ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๊ฐ„์— ์ƒํƒœ๋ฅผ ๊ณต์œ  ํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ‘‰ ๋ฌผ๋ก  ์ด๋•Œ๊นŒ์ง€ํ•œ ๋ถ€๋ถ„๋„ ์ƒํƒœ๊ณต์œ ์ง€๋งŒ ๊ทธ๊ฒƒ๋“ค์€ props์˜ ํ˜•ํƒœ์ด๊ณ  ์ด๊ฒƒ์€ provider๋ฅผ ํ†ตํ•ด ๊ณต์œ ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€