Jieunny의 블로그

Section14. React.js 및 TypeScript 본문

CodeStates/TS 스터디

Section14. React.js 및 TypeScript

Jieunny 2023. 3. 27. 10:03

𝟭. React + TypeScript 프로젝트 설정하기

✔️ 프로젝트 생성하기

1️⃣ create-react-app 으로 리액트 프로젝트 시작하기

npm install -g create-react-app
// 설치
create-react-app . --typescript

➰ tsconfig.json 파일이 만들어진다.

➰ App.ts가 아닌 App.tsx 파일이 만들어진다.

    ㄴ 타입스크립트만 쓸 수 있는 게 아니라 jsx코드(타입스크립트안에 자바스크립트)도 쓸 수 있다.

 

2️⃣ src 폴더 정리하기

src

    ㄴ App.tsx

        ㄴ App.tsx 파일에 필요없는 import, 컴포넌트 지우기

    ㄴ index.css

html {
    font-family: sans-serif;
}

body {
    margin: 0;
}

    ㄴ index.tsx

    ㄴ react-app-env.d.ts

 

𝟮. React와 TypeScript는 어떻게 같이 작동할까?

// App.tsx

import React from 'react';

const App: React.FC = () => {
    return <div className="App"></div>;
};

export default App;

React.FC는 리액트 타입 패키지에서 제공되는 function 타입이며 FC는 'function component'의 약자이다. 

App이 평범한 function 이 아니라 function Component라는 것을 표시한다.

➰ 만약 App 컴포넌트안에 return문을 없애면 에러가 발생한다 -> 타입스크립트는 이런 실수를 감지해서 문제를 방지한다.

 

𝟯. Props로 작업하기 & Props의 타입

✔️ TodoList 컴포넌트 만들기

1️⃣ src폴더 안에 components 폴더 만들고 TodoList.tsx 파일 생성

// 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;
// TodoList.tsx
// 할일 목록을 불러올 컴포넌트

import React from 'react';

interface TodoListProps {
// props의 구조를 정의해준다.
	items: {id: strind, text: string}[];
    // 배열의 모든 항목은 id와 text키가 있어야 한다.
};

const TodoList: React.FC<TodoListProps> = props => {
// 꼭 화살표 함수일 필요는 없고 function 키워드로 만들어도 된다.
// 타입스크립트를 사용할 때는 props의 타입도 지정해줘야 한다.
// 단순한 function component가 아닌 받게 될 props도 알려준다.
    return (
        <ul>
            {props.items.map(todo => <li key={todo.id}> {todos.text} </li>)}
            // todos 배열 돌면서 할일 목록 하나씩 불러오기
        </ul>;
    );
};

export default TodoList;

 

𝟰. 'ref'로 사용자 입력 받기

✔️ NewTodo 컴포넌트 만들기

1️⃣ components 폴더 안에 NewTodo.tsx 파일 생성

// 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">
            <NewTodo />
            <TodoList items={todos} />
        </div>
    );
};

export dafault App;
// NewTodo.tsx
// 할 일 추가 할 수 있는 컴포넌트

import React, { useRef } from 'react';

const NewTodo: React.FC = () => {
    const textInputRef = useRef<HTMLInputElement>(null);
    // ref 오브젝트가 들어온다(DOM을 조작할 수 있게 된다)
    // 타입스크립트에서는 ref 안에 어떤 타입이 저장될 지 알아야 사용할 수 있다.
    // 처음 렌더링 될 때는 아직 밑에있는 것들이 렌더링 되지 않아 아무것도 가르키지 않으므로 null을 준다.
    
    const todoSubmitHandler = (event: React.FormEvent) => {
    // form이 제출될 때 마다 실행 될 함수
        event.preventDefault();
        // 새로고침 방지 
        const enteredText = textInputRef.current!.value;
        // ref를 사용할 때는 current를 사용해야 한다(이때 null이 아니라는 것을 !을 사용해서 알려준다)
    };
    
    return <form onSubmit={todoSubmitHandler}>
        <div>
            <label htmlFor="todo-text">Todo Text</label>
            <input type="text" id="todo-text" ref={textInputRef}/>
            // ref
        </div>
        <button type="submit">ADD TODO</button>
    </form>;
};

export default NewTodo;

 

𝟱. Cross-Component 커뮤니케이션

✔️ NewTodo.tsx에서 입력 받은 텍스트를 NewTodo컴포넌트에서 App컴포넌트로 옮기기

 // 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'}];
    
    const todoAddHandler = (text: string) => {
         
    };
    
    return (
        <div className="App">
            <NewTodo onAddTodo={todoAddHandler}/>
            // onAddTodo 함수를 props로 전달한다.
            <TodoList items={todos} />
        </div>
    );
};

export dafault App;
// NewTodo.tsx
// 할 일 추가 할 수 있는 컴포넌트

import React, { useRef } from 'react';

type NewTodoProps = {
// props 타입 정의하기
// type 대신 interface 사용도 가능
    onAddTodo: (todoText: string) => void;
};

const NewTodo: React.FC<NewTodoProps> = props => {
// props를 받을 땐 타입도 받아야한다.
    const textInputRef = useRef<HTMLInputElement>(null);
    // ref 오브젝트가 들어온다(DOM을 조작할 수 있게 된다)
    // 타입스크립트에서는 ref 안에 어떤 타입이 저장될 지 알아야 사용할 수 있다.
    // 처음 렌더링 될 때는 아직 밑에있는 것들이 렌더링 되지 않아 아무것도 가르키지 않으므로 null을 준다.
    
    const todoSubmitHandler = (event: React.FormEvent) => {
    // form이 제출될 때 마다 실행 될 함수
        event.preventDefault();
        // 새로고침 방지 
        const enteredText = textInputRef.current!.value;
        // ref를 사용할 때는 current를 사용해야 한다(이때 null이 아니라는 것을 !을 사용해서 알려준다)
    	props.onAddTodo(enteredText);
    };
    
    return <form onSubmit={todoSubmitHandler}>
        <div>
            <label htmlFor="todo-text">Todo Text</label>
            <input type="text" id="todo-text" ref={textInputRef}/>
            // ref
        </div>
        <button type="submit">ADD TODO</button>
    </form>;
};

export default NewTodo;

 

𝟲. 상태 및 타입 작업하기

✔️ todos 배열을 state로 관리하기

1️⃣ App.tsx 파일 수정하기

 // App.tsx

import React, { useState } from 'react';

import TodoList from './components/TodoList';
import NewTodo from './components/NewTodo';
import { Todo } from './todo.model';
// todos 배열의 타입을 담고있는 Todo interface를 받아온다.

const App: React.FC = () => {
    const [todos, setTodos] = useState<Todo[]>([]);
    // (1) const [todos, setTodos] = useState([]);
    // (1)처럼 선언하면 타입스크립트는 todos 배열이 항상 비어있는 배열임을 기대한다.
    // (2) const [todos, setTodos] = useState<{id: string, text: string}>([]);
    // (2) 처럼 선언해도 되지만, 여러 위치에서 사용할 것이기 때문에 타입을 새 파일로 만들어준다.
    
    const todoAddHandler = (text: string) => {
        setTodos([{id: Math.random().toString(), text: text}]);
        // todos 배열을 새로운 todo가 추가 되어 있는 배열로 바꾼다.
    };
    
    return (
        <div className="App">
            <NewTodo onAddTodo={todoAddHandler}/>
            // onAddTodo 함수를 props로 전달한다.
            <TodoList items={todos} />
        </div>
    );
};

export dafault App;

 

2️⃣ src 폴더 안에 todo.model.ts 파일 생성하기

// todo.model.ts

export interface Todo {
    id: string;
    text: string;
}

 

𝟳. 더 나은 상태 관리하기

✔️ todos 배열에 새  데이터를 추가할 때 함수 안에 함수를 이용하기

 // App.tsx

import React, { useState } from 'react';

import TodoList from './components/TodoList';
import NewTodo from './components/NewTodo';
import { Todo } from './todo.model';
// todos 배열의 타입을 담고있는 Todo interface를 받아온다.

const App: React.FC = () => {
    const [todos, setTodos] = useState<Todo[]>([]);
    // (1) const [todos, setTodos] = useState([]);
    // (1)처럼 선언하면 타입스크립트는 todos 배열이 항상 비어있는 배열임을 기대한다.
    // (2) const [todos, setTodos] = useState<{id: string, text: string}>([]);
    // (2) 처럼 선언해도 되지만, 여러 위치에서 사용할 것이기 때문에 타입을 새 파일로 만들어준다.
    
    const todoAddHandler = (text: string) => {
        setTodos(prevTodos => [...todos, {{id: Math.random().toString(), text: text}]);
        // (2) 함수에 함수를 전달한다.
        // 이전 상태를 담고 있는 prevTodos를 불러와서 새 데이터가 추가 된 새 상태를 리턴한다.
        // 이런 식으로 구현하면 prevTodos가 항상 최신 상태를 가지고 있다는 것이 보장된다.
        
        // (1) setTodos([...todos, {id: Math.random().toString(), text: text}]);
        // todos 배열을 새로운 todo가 추가 되어 있는 배열로 바꾼다.
        // 원래 배열을 스프레드 문법으로 불러오고, 뒤에 새 데이터를 추가해준다.
        // (1) 처럼 하는 건 가장 좋은 방법이 아니다 -> 상태 업데이트가 실행될 때 앞에 ...으로 불러온
        // todos 값이 최신 상태가 아닐 수 있다.
    };
    
    return (
        <div className="App">
            <NewTodo onAddTodo={todoAddHandler}/>
            // onAddTodo 함수를 props로 전달한다.
            <TodoList items={todos} />
        </div>
    );
};

export dafault App;

 

𝟴. 더 많은 props 및 상태 작업

✔️ 할 일 지우는 기능 구현

 // App.tsx

import React, { useState } from 'react';

import TodoList from './components/TodoList';
import NewTodo from './components/NewTodo';
import { Todo } from './todo.model';
// todos 배열의 타입을 담고있는 Todo interface를 받아온다.

const App: React.FC = () => {
    const [todos, setTodos] = useState<Todo[]>([]);
    // (1) const [todos, setTodos] = useState([]);
    // (1)처럼 선언하면 타입스크립트는 todos 배열이 항상 비어있는 배열임을 기대한다.
    // (2) const [todos, setTodos] = useState<{id: string, text: string}>([]);
    // (2) 처럼 선언해도 되지만, 여러 위치에서 사용할 것이기 때문에 타입을 새 파일로 만들어준다.
    
    const todoAddHandler = (text: string) => {
    // todos 배열에 새 데이터 추가하는 함수
        setTodos(prevTodos => [...todos, {{id: Math.random().toString(), text: text}]);
        // (2) 함수에 함수를 전달한다.
        // 이전 상태를 담고 있는 prevTodos를 불러와서 새 데이터가 추가 된 새 상태를 리턴한다.
        // 이런 식으로 구현하면 prevTodos가 항상 최신 상태를 가지고 있다는 것이 보장된다.
        
        // (1) setTodos([...todos, {id: Math.random().toString(), text: text}]);
        // todos 배열을 새로운 todo가 추가 되어 있는 배열로 바꾼다.
        // 원래 배열을 스프레드 문법으로 불러오고, 뒤에 새 데이터를 추가해준다.
        // (1) 처럼 하는 건 가장 좋은 방법이 아니다 -> 상태 업데이트가 실행될 때 앞에 ...으로 불러온
        // todos 값이 최신 상태가 아닐 수 있다.
    };
    
    const todoDeleteHandler = (todoId: string) => {
    // todos 배열에서 지우기 누른 할 일 지우는 함수
        setTodos(prevTodos => {
            return prevTodos.filter(todo => todo.id !== todoId);
            // filter 함수 써서 삭제할 todo의 id와 다른 id를 가진 todo만 가져와서 새 배열을 만든다. 
        });
    };
    
    return (
        <div className="App">
            <NewTodo onAddTodo={todoAddHandler}/>
            // onAddTodo 함수를 props로 전달한다.
            <TodoList items={todos} onDeleteTodo={todoDeletehandler}/>
        </div>
    );
};

export dafault App;
// TodoList.tsx
// 할일 목록을 불러올 컴포넌트

import React from 'react';

interface TodoListProps {
// props의 구조를 정의해준다.
    items: {id: strind, text: string}[];
    // 배열의 모든 항목은 id와 text키가 있어야 한다.
    
    onDeleteTodo: (id: string) => void;
    // props로 삭제 함수도 받아오기 때문에 props 타입 정의하는 곳에 타입을 정의해줘야한다.
};

const TodoList: React.FC<TodoListProps> = props => {
// 꼭 화살표 함수일 필요는 없고 function 키워드로 만들어도 된다.
// 타입스크립트를 사용할 때는 props의 타입도 지정해줘야 한다.
// 단순한 function component가 아닌 받게 될 props도 알려준다.
    return (
        <ul>
            {props.items.map(todo => 
                <li key={todo.id}> 
                    <span>{todos.text}</span>
                    <button onClick={props.onDeleteTodo.bind(null, todo.id)}>DELETE</button>
                    // 삭제 버튼 만들기
                    // bind의 두 번째 매개변수가 onDeleteTodo가 받을 첫번째 매개변수가 된다.
                    // ?????
                </li>)}
            // todos 배열 돌면서 할일 목록 하나씩 불러오기
        </ul>;
    );
};

export default TodoList;

 

𝟵. 다른 React 기능의 타입(Redux or 라우팅)

1️⃣ redux는 공식문서에도 타입스크립트 적용 방법이 쓰여있다.

2️⃣ react-router-dom은 그렇지 않아서 그냥 설치하고 사용하면 에러가 뜬다.

    ㄴ npm install --save-dev @types/tract-router-dom 으로 설치해서 사용해라.