Dandy Now!
  • [TypeScript] React.js에서 타입스크립트 사용(udemy 강의 "Typescript :기초부터 실전형 프로젝트까지 with React + NodeJS" 섹션 14)
    2024년 03월 06일 11시 09분 33초에 업로드 된 글입니다.
    작성자: DandyNow
    728x90
    반응형

    섹션 14: React.js 및 TypeScript

    📌 React.js + TypeScript 프로젝트 생성

    .은 현재 경로에 프로젝트를 생성하는 것을 말한다.

    npx create-react-app . --template typescript

    😉 공식 문서 : https://create-react-app.dev/docs/adding-typescript/

     

    📌 React.FC

    // src/app.tsx
    
    import React from "react";
    
    // React.FC는 리액트 함수 컴포넌트 사용함을 의미
    const App: React.FC = () => {
      return <div className="App"></div>;
    }
    
    export default App;

     

    📌 React.FC와 <>

    React.FC에 화살 괄호 <>를 사용해 props의 타입을 지정할 수 있다. 여기에서는 TodoListProps 인터페이스를 생성하여 타입을 지정했다.

    // src/components/TodoList.tsx
    
    import React from "react";
    
    // 인터페이스로 prop로 받을 items 객체 타입 정의
    interface TodoListProps {
      items: { id: string; text: string }[];
    }
    
    const TodoList: React.FC<TodoListProps> = (props) => { // React.FC 화살괄호 안에 TodoListProps 인터페이스 추가
      return (
        <ul>
          {props.items.map((todo) => (
            <li key={todo.id}>{todo.text}</li>
          ))}
        </ul>
      );
    };
    
    export default TodoList;
    더보기
    // src/app.tsx
    
    import React from "react";
    import TodoList from "./components/TodoList";
    
    const App: React.FC = () => {
      const todos = [{ id: "t1", text: "Finish the course" }];
    
      return (
        <div className="App">
          <TodoList items={todos} />
        </div>
      );
    };
    
    export default App;

     

    📌 React.FormEvent, HTMLInputElement

    input 창에 텍스트를 입력하고 "ADD TODO" 버튼을 누르면 콘솔에 해당 텍스트가 찍히는 코드이다. input event의 타입은 React.FormEvent이다. useRef의 타입은 HTMLInputElement이다.

    // src/components/NewTodo.tsx
    
    import React, { useRef } from "react";
    
    const NewTodo: React.FC = () => {
      const textInputRef = useRef<HTMLInputElement>(null);
    
      const todoSubmitHandler = (event: React.FormEvent) => {
        event.preventDefault();
        const enteredText = textInputRef.current!.value;
        console.log(enteredText);
      };
      return (
        <form onSubmit={todoSubmitHandler}>
          <div>
            <label htmlFor="todo-text">Todo Text</label>
            <input type="text" id="todo-text" ref={textInputRef} />
          </div>
          <button type="submit">ADD TODO</button>
        </form>
      );
    };
    
    export default NewTodo;
    더보기
    // src/app.tsx
    
    import React from "react";
    import TodoList from "./components/TodoList";
    import NewTodo from "./components/NewTodo";
    
    const App: React.FC = () => {
      const todos = [{ id: "t1", text: "Finish the course" }];
    
      return (
        <div className="App">
          <TodoList items={todos} />
          {/* NewTodo 컴포넌트 추가 */}
          <NewTodo />
        </div>
      );
    };
    
    export default App;

     

    📌 type alias와 함수 타입

    props로 함수를 넘겨 자식 컴포넌트에서 input으로 입력된 텍스트 값을 부모 컴포넌트에서 이용하고자 한다. 이때 props로 넘긴 함수의 타입을 정의해 주어야 한다.

    // src/components/NewTodo.tsx
    
    import React, { useRef } from "react";
    
    // type alias를 이용해 onAddTodo() 함수의 타입을 정의
    type NewTodoProps = {
      onAddTodo: (todoText: string) => void;
    };
    
    const NewTodo: React.FC<NewTodoProps> = (props) => { // React.FC의 <>에 정의한 타입 지정
      const textInputRef = useRef<HTMLInputElement>(null);
    
      const todoSubmitHandler = (event: React.FormEvent) => {
        event.preventDefault();
        const enteredText = textInputRef.current!.value;
        props.onAddTodo(enteredText); // enteredText 값을 onAddTodo() 함수를 이용해 부모 컴포넌트로 넘김
      };
      return (
        <form onSubmit={todoSubmitHandler}>
          <div>
            <label htmlFor="todo-text">Todo Text</label>
            <input type="text" id="todo-text" ref={textInputRef} />
          </div>
          <button type="submit">ADD TODO</button>
        </form>
      );
    };
    
    export default NewTodo;
    더보기
    // src/App.tsx
    
    import React from "react";
    import TodoList from "./components/TodoList";
    import NewTodo from "./components/NewTodo";
    
    const App: React.FC = () => {
      const todos = [{ id: "t1", text: "Finish the course" }];
    
      // props로 넘길 함수 정의
      const todoAddHandler = (text: string) => {
        console.log(text);
      };
    
      return (
        <div className="App">
          {/* NewTodo 컴포넌트 props로 todoAddHandler() 함수 넘김 */}
          <NewTodo onAddTodo={todoAddHandler} />
          <TodoList items={todos} />
        </div>
      );
    };
    
    export default App;

     

    📌 useState의 경우

    // src/App.tsx
    
    import React, { useState } from "react";
    import TodoList from "./components/TodoList";
    import NewTodo from "./components/NewTodo";
    import { Todo } from "./todo.model"; // 타입 정의 추가
    
    const App: React.FC = () => {
      // const [todos, setTodos] = useState<{id: string; text: string}[]>([]); // props로 받는 객체 타입을 직접 정의한 경우
      const [todos, setTodos] = useState<Todo[]>([]); // props로 받는 객체 타입을 todo.model.ts 파일에 정의한 경우
    
      // 기존 상태값 업데이트
      const todoAddHandler = (text: string) => {
        setTodos((prevTodos) => [
          ...prevTodos,
          { id: Math.random().toString(), text: text },
        ]);
      };
    
      return (
        <div className="App">
          <NewTodo onAddTodo={todoAddHandler} />
          <TodoList items={todos} />
        </div>
      );
    };
    
    export default App;
    // src/todo.model.ts
    
    export interface Todo {
      id: string;
      text: string;
    }

     

    📌 todo 삭제와 bind

    버튼의 클릭 이벤트로 함수를 호출할 때 bind나 화살 괄호를 사용하지 않으면 에러가 발생한다. 에러의 내용은 다음과 같다.

    Type 'void' is not assignable to type 'MouseEventHandler<HTMLButtonElement> | undefined'.
    ts(2322)
    index.d.ts(2427, 9): The expected type comes from property 'onClick' which is declared here on type 'DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>'

     

    아래 코드에는 button 태그의 onClick 속송에 bind와 화살 괄호를 각각 사용해본 코드가 포함되어 있다.

    // src/componets/TodoList.tsx
    
    import React from "react";
    
    interface TodoListProps {
      items: { id: string; text: string }[];
      onDeleteTodo: (id: string) => void; // 타입 추가
    }
    
    const TodoList: React.FC<TodoListProps> = (props) => {
      return (
        <ul>
          {props.items.map((todo) => (
            <li key={todo.id}>
              <span>{todo.text}</span>
              {/* bind 사용한 경우 */}
              <button onClick={props.onDeleteTodo.bind(null, todo.id)}>
                DELETE(bind)
              </button>
              {/* 화살표 함수 사용한 경우 */}
              <button onClick={() => props.onDeleteTodo(todo.id)}>DELETE</button>
            </li>
          ))}
        </ul>
      );
    };
    
    export default TodoList;
    // src/App.tsx
    
    import React, { useState } from "react";
    import TodoList from "./components/TodoList";
    import NewTodo from "./components/NewTodo";
    import { Todo } from "./todo.model";
    
    const App: React.FC = () => {
      const [todos, setTodos] = useState<Todo[]>([]);
    
      const todoAddHandler = (text: string) => {
        setTodos((prevTodos) => [
          ...prevTodos,
          { id: Math.random().toString(), text: text },
        ]);
      };
    
      // todo 삭제를 당담하는 함수
      const todoDeleteHandler = (todoId: string) => {
        setTodos((prevTodos) => {
          return prevTodos.filter((todo) => todo.id !== todoId);
        });
      };
    
      return (
        <div className="App">
          <NewTodo onAddTodo={todoAddHandler} />
          
          {/* todoDeleteHandler 함수를 props로 보냄 */}
          <TodoList items={todos} onDeleteTodo={todoDeleteHandler} />
        </div>
      );
    };
    
    export default App;

     

    📌 스타일 추가

    스타일을 import하는 방식에 유의!

    // src/components/TodoList.tsx
    
    import React from "react";
    import "./TodoList.css"; // 추가
    
    // (생략)
    // src/components/NewTodo
    
    import React, { useRef } from "react";
    import "./NewTodo.css"; // 추가
    
    // (타입 정의 생략)
    
    const NewTodo: React.FC<NewTodoProps> = (props) => {
      // (생략)
      return (
        <form onSubmit={todoSubmitHandler}>
        
          {/* className 속성 추가 */}
          <div className="form-control">
            <label htmlFor="todo-text">Todo Text</label>
            <input type="text" id="todo-text" ref={textInputRef} />
          </div>
          <button type="submit">ADD TODO</button>
        </form>
      );
    };
    
    export default NewTodo;
    더보기
    /* src/components/TodoList.css */
    
    ul {
      list-style: none;
      width: 90%;
      max-width: 40rem;
      margin: 2rem auto;
      padding: 0;
    }
    
    li {
      margin: 1rem 0;
      padding: 1rem;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
      border-radius: 6px;
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    /* src/components/NewTodo.css */
    
    form {
      width: 90%;
      max-width: 40rem;
      margin: 2rem auto;
    }
    
    .form-control {
      margin-bottom: 1rem;
    }
    
    label, input {
      display: block;
      width: 100%;
    }
    
    label {
      font-weight: bold;
    }
    
    input {
      font: inherit;
      border: 1px solid #ccc;
      padding: 0.25rem;
    }
    
    input:focus {
      outline: none;
      border-color: #50005a;
    }
    
    button {
      background: #50005a;
      border: 1px solid #50005a;
      color: white;
      padding: 0.5rem 1.5rem;
      cursor: pointer;
    }
    
    button:focus {
      outline: none;
    }
    
    button:hover,
    button:active {
      background: #6a0a77;
      border-color: #6a0a77;
    }
    728x90
    반응형
    댓글