Jieunny의 블로그
Section14. React.js 및 TypeScript 본문
𝟭. 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 으로 설치해서 사용해라.
'CodeStates > TS 스터디' 카테고리의 다른 글
[TS] 날씨 검색 서비스 구현하기 (0) | 2023.03.31 |
---|---|
Section11. TS와 함께 Webpack 사용하기 (0) | 2023.03.24 |
Section10. 모듈 및 네임스페이스 (0) | 2023.03.23 |
Section9. Drag & Drop 프로젝트 만들기 (2) | 2023.03.20 |
Section8. 데코레이터 (0) | 2023.03.03 |