CRA를 사용해서 타입스크립트 프로젝트를 생성할 때
npx create-react-app [프로젝트명] --template=typescript
// 혹은
npx create-react-app [프로젝트명] --template typescript
이미 생성된 프로젝트에 타입스크립트를 추가할 때
npm install typescript @types/node @types/react @types/react-dom @types/jest
/// <reference types="react-scripts" />
const App: React.FC = () => {
return <div className='App'></div>;
};
React. : 리액트에서 제공하는 타입
(node_modules의 @types 폴더에서 찾을 수 있음)
FC : Function Component(jsx를 리턴)
React.FC 는 해당 컴포넌트가 props로 children를 가지고 있음을 암시적으로 전달
children을 다루고 있지 않은 컴포넌트에 children을 넘겨주어도 타입 에러가 발생하지 않는다
const NoChildren: React.FC = () => {
return <div>Hello, world!</div>;
};
const App = () => {
<NoChildren>Helllllllo!!!</NoChildren>;
};
해당 암묵적 선언은 18버전에서 제거됨
children을 사용하고 싶을 때에는 children: React.ReactNode
로 따로 타입을 지정해줘야 함
컴포넌트에 리턴값이 있을 경우 : JSX.Element
변수에 타입을 설정해야 하는 경우 : React.FC<type>
타입스크립트를 리액트와 같이 사용할 때에는 이용하는 타입을 명확하게 지정해야 함
컴포넌트에 프롭을 사용한다면 타입스크립트로 이 프롭이 어떻게 생겼는지, 어떤 구조를 가지고 있는지 명시
interface TodoListProps {
todos: { id: number; text: string }[];
}
// 프롭으로 받을 타입을 <> 안에 명시
const TodoList: React.FC<TodoListProps> = ({ todos }) => {
return (
...
);
};
Parameter 'props' implicitly has an 'any' type
React.FC 는 제네릭 타입
이미 내부에서 홀화살괄호를 사용해 정의된 상태
따라서 Todos:React.FC<{...}> 는 새로운 제네릭 타입을 만드는 것이 아니라, 내부적으로 사용되는 제네릭 타입에 구체적인 값을 추가하는 것
...에 필요한 형태의 props 정의
FC는 'key'같은 특별한 프로퍼티를 컴포넌트에 추가해 사용할 수 있게 해줌
const NewTodo = () => {
const submitHandler = (event: React.FormEvent) => {
event.preventDefault();
};
return (
<form onSubmit={submitHandler}>
...
</form>
);
};
onSubmit에 전달되는 evnet는 FormEvent임을 명시해야 함
input 안의 내용을 불러오는 방법
useRef은 제네릭 타입으로 정의되어 있음
따라서 해당 useRef으로 생성할 레퍼런스가 어떤 타입일지 명확히 설정해야 함
useRef 레퍼런스가 다른 값에 할당되어있을 수도 있기 때문에 초기값으로 null을 지정해줘야 함
(지정하지 않으면 input에 ref를 연결할 때 오류 발생)
useRef이 연결된 부분이 input이기 때문에 HTMLInputElement 타입 전달
const NewTodo = () => {
const todoTextInputRef = useRef<HTMLInputElement>(null);
const submitHandler = (event: React.FormEvent) => {
event.preventDefault();
const enteredText = todoTextInputRef.current!.value;
};
return (
...
<input type='text' id='text' ref={todoTextInputRef} />
...
);
};
todoText.inputRef.current.value;
에서 타입스크립트는 해당 코드가 실행될 때 current값이 있음을 확인하지 못하므로, 해당 값이 있음을 확신시켜주기 위해 ! 추가
( ! : 해당 값이 절대로 null 이 아님을 확신할 때 사용
? : 해당 값이 있는지 없는지 확신할 수 없을 때 사용.
일단 값에 접근해보고 접근이 가능하다면 입력된 값 사용, 접근이 불가능하다면 null 값 저장 )
어플리케이션의 여러 위치에서 / 자주 사용하는 아이템인 경우 ().model.ts 처럼 새로운 파일을 만들어 모음
//todo.module.ts
export interface Todo {
id: number;
text: string;
}
//App.tsx
const [todos, setTodos] = useState<Todo[]>([]);
useState를 단순히 빈배열로 초기화만 할 경우, 타입스크립트는 해당 상태가 항상 빈 배열일 것이라고만 추측
해당 상태가 어떻게 구성될 것인지를 따로 알려줘야 할 필요가 있음
useState는 상태가 비동기적으로 업데이트 되기 때문에 변경사항이 즉시 반영되지 않음
따라서setTodos([...todos, { id: todos.length, text: text }]);
코드의 경우 해당 todos가 가장 최신 상태가 아닐 수 있기 때문에, 상태를 업데이트 할 때 최신 상태를 사용할 것을 보장하기 위해 함수 사용
setTodos((prev) => [...prev, { id: todos.length, text: text }]);
// todo.ts
class Todo {
id: string;
text: string;
constructor(todoText: string) {
this.text = todoText;
this.id = new Date().toLocaleDateString();
}
}
//App.tsx
const todos = [new Todo('할 일'), new Todo('할 일2')];
//Todos.tsx
const Todos: React.FC<{ items: Todo[] }> = (props) => {
...
};
props으로 function을 받을 때
const NewTodo: React.FC<{ onAddTodo: (text: string) => void }> = (props) => {
const submitHandler = (event: React.FormEvent) => {
...
props.onAddTodo(enteredText);
};
};
다른 타입의 props를 받을 때와 마찬가지로 타입 지정 필요
onAddTodo 함수는 반환 값이 없기 때문에 void 지정
const TodoItem: React.FC<{
text: string;
onRemoveTodo: (event: React.MouseEvent) => void;
}> = (props) => {
return (
<li className={classes.item} onClick={props.onRemoveTodo}>
{props.text}
</li>
);
};
이 경우 인수를 사용하지 않기 때문에, 인수의 타입(event: React.MouseEvent)을 정의하는건 선택사항이 됨
const Todos: React.FC<{ items: Todo[]; onRemoveTodo: (id: string) => void }> = (
props
) => {
return (
...
<TodoItem
...
onRemoveTodo={props.onRemoveTodo.bind(null, item.id)}
...
};
bind를 사용해서 실행할 함수를 미리 설정 가능
onRemoveTodo가 매개변수로 받을 값을 두번째로 전달
(해당 id값을 전달하기 위해 bind 사용, this 키워드는 현재 필요 없기 때문에 null로 두고 두 번째 매개변수가 해당 함수가 받을 첫번째 매개변수가 됨)
삭제 기능을 추가하기 위해 id를 인자로 받는 함수를 TodoList로 전달, button의 onClick 이벤트에 해당 함수를 연결한다
interface TodoListProps {
todos: { id: number; text: string }[];
onDeleteTodo: (id: number) => void;
// 프롭으로 받는 아이템에 함수 추가
}
const TodoList: React.FC<TodoListProps> = ({ todos, onDeleteTodo }) => {
return (
<ul>
{todos.map((ele) => (
...
<button onClick={onDeleteTodo.bind(null, ele.id)}>Del</button>
// 해당 id값을 전달하기 위해 bind 사용, this 키워드는 현재 필요 없기 때문에 null로 두고 두 번째 매개변수가 해당 함수가 받을 첫번째 매개변수가 됨
...
))}
</ul>
);
};
Context API : 데이터를 전역적으로 사용할 수 있게 해주는 리액트의 내장 기능
Props Drilling 문제를 피하고자 할 때 사용 가능
상태관리 도구가 아님(상태 관리는 직접 해야 함, 전역적으로 상태를 공유해주는 기능만 수행)
useContext를 사용한 모든 컴포넌트에 리렌더링 문제가 발생
import React, { useState } from 'react';
import Todo from '../models/todo';
type TodosContextObj = {
items: Todo[];
addTodo: (text: string) => void;
removeTodo: (id: string) => void;
};
export const TodosContext = React.createContext<TodosContextObj>({
items: [],
addTodo: () => {},
removeTodo: (id: string) => {},
});
// 18v 부터 React.FC가 children을 암묵적으로 허용하던 부분이 사라졌기 때문에, ReactNode로 새롭게 children 타입을 정해줘야 함
const TodosContextProvider: React.FC<{ children: React.ReactNode }> = (
props
) => {
// App.tsx에서 사용하던 상태관리 코드 가져오기
const [todos, setTodos] = useState<Todo[]>([]);
const addTodoHandler = (text: string) => {
const newTodo = new Todo(text);
setTodos((prev) => [...prev, newTodo]);
};
const removeTodoHandler = (todoId: string) => {
setTodos((prev) => {
return prev.filter((todo) => todo.id !== todoId);
});
};
// Provider로 전달할 value값 지정
const contextValue: TodosContextObj = {
items: todos,
addTodo: addTodoHandler,
removeTodo: removeTodoHandler,
};
return (
<TodosContext.Provider value={contextValue}>
{props.children}
</TodosContext.Provider>
);
};
export default TodosContextProvider;
//App.tsx
function App() {
return (
// useContext를 사용하기 위해서는 해당 컨텍스트를 사용하는 컴포넌트들을 Provider로 감싸야 함
<TodosContextProvider>
<NewTodo />
<Todos />
</TodosContextProvider>
);
}
// Todos.tsx
const Todos: React.FC = () => {
// props 대신 Context로 상태값을 가지고 와서 사용할 수 있음(코드를 더 깔끔하게 사용 가능)
const todosCtx = useContext(TodosContext);
return (
<ul className={classes.todos}>
{todosCtx.items.map((item) => (
<TodoItem
key={item.id}
text={item.text}
onRemoveTodo={todosCtx.removeTodo.bind(null, item.id)}
/>
))}
</ul>
);
};
export default Todos;
npm i @types/react-router-dom 설치 필요