
📅 2025-11-28
➡️ TypeScript 타입 단언, 타입 좁히기, 투두리스트 실습에 대해 새롭게 알게 된 것 또는 헷갈리는 부분 정리
any로 들어오는 값을 원하는 타입으로 처리할 때const value: any = "Hello";
const length = (value as string).length;
export / import로 외부와 기능 공유// math.ts
export function add(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
// app.ts
import { add, subtract } from "./math";
console.log(add(2, 3)); // 5
console.log(subtract(5, 3)); // 2
namespace MathUtils {
export function add(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
}
console.log(MathUtils.add(2, 3));
console.log(MathUtils.subtract(5, 3));
// types/todo.ts
export type Todo = {
id: string;
title: string;
completed: boolean;
};
export type ToggleTodo = Omit<Todo, "title">;
// App.tsx
import { Todo, ToggleTodo } from "./types/todo";import type)모듈 전체를 타입 전용으로 가져오기
import type { Todo, ToggleTodo } from "./types/todo";
value + type 섞어 가져올 때
import 항목 앞에 type 키워드 개별 지정import { type Todo, type ToggleTodo } from "./types/todo";
declear module 사용// 라이브러리 import
import "some-library";
// 모듈 보강
declare module "some-library" {
export interface SomeType {
newProperty: string;
}
}
// 사용
import { SomeType } from "some-library";
const obj: SomeType = {
newProperty: "Hello",
};
window 등)에 타입을 추가해서 사용declare global {
interface Window {
myCustomProperty: string;
}
}
window.myCustomProperty = "Hello World";
console.log(window.myCustomProperty);
declare는 컴파일러에게 이미 존재하는 타입이라고 알려주는 역할typeof, instanceof, in, 사용자 정의 타입 가드 등 사용typeof 타입 가드function print(value: string | number) {
if (typeof value === "string") {
console.log(`String: ${value}`);
} else {
console.log(`Number: ${value}`);
}
}instanceof 타입 가드class Dog {
bark() {
console.log("Woof!");
}
}
class Cat {
meow() {
console.log("Meow!");
}
}
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark();
} else {
animal.meow();
}
}in 연산자 타입 가드interface Fish {
swim: () => void;
}
interface Bird {
fly: () => void;
}
function move(animal: Fish | Bird) {
if ("swim" in animal) {
animal.swim();
} else {
animal.fly();
}
}function formatValue(value: string | number | boolean) {
if (typeof value === "string") {
return value.toUpperCase();
} else if (typeof value === "number") {
return value.toFixed(2);
} else {
return value ? "TRUE" : "FALSE";
}
}
console.log(formatValue("hello")); // HELLO
console.log(formatValue(3.14159)); // 3.14
console.log(formatValue(true)); // TRUEexport type Todo = {
id: string;
title: string;
completed: boolean;
};title 제외)export type ToggleTodo = Omit<Todo, 'title'>;TodoItem의 props 타입 정의export type TodoItemProps = {
todoList: Todo[];
onToggle: ({ id, completed }: ToggleTodo) => void;
onEdit: (id: Todo['id']) => void;
onDelete: (id: Todo['id']) => void;
};투두리스트 패칭
const BASE_URL = 'http://localhost:4000/todos';
const [todos, setTodos] = useState<Todo[]>([]);
const fetchTodos = async () => {
const res = await fetch(BASE_URL);
if (!res.ok) {
throw new Error('todo를 받아오는데 실패했습니다.');
}
const data = await res.json();
return data;
};
useEffect(() => {
fetchTodos().then((data) => setTodos(data));
}, []);
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<span>{todo.completed ? '완료' : '진행중'} | {todo.title}</span>
</li>
))}
</ul>
Todo[] 타입 사용투두 추가
const [title, setTitle] = useState<string>('');
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setTitle(e.target.value);
};
// 추가
const handleAddTodo = async () => {
const newTodo: Todo = {
id: crypto.randomUUID(),
title: title,
completed: false,
};
await fetch(BASE_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newTodo),
});
setTodos((prev) => [...prev, newTodo]);
setTitle('');
};
<div>
<input type="text" value={title} onChange={handleChange} />
<button onClick={handleAddTodo}>추가</button>
</div>
e의 타입 → React.ChangeEvent<HTMLInputElement>
const handleDeleteTodo = async (id: Todo['id']) => {
await fetch(`${BASE_URL}/${id}`, {
method: 'DELETE',
});
setTodos((prev) => prev.filter((todo) => todo.id != id));
};<button onClick={() => handleDeleteTodo(todo.id)}>삭제</button>id: Todo['id'] id 타입만 필요할 경우 이렇게 사용할 수도 있음투두 수정
const handleEditTodo = async (id: Todo['id']) => {
const updateTitle = window.prompt('변경할 내용을 입력해 주세요');
if (updateTitle === null) return;
if (!updateTitle.trim()) {
alert('내용이 비어 있습니다.');
return;
}
await fetch(`${BASE_URL}/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: updateTitle }),
});
setTodos((prev) => prev.map((todo) => (todo.id === id ? { ...todo, title: updateTitle } : todo)));
};
<button onClick={() => handleEditTodo(todo.id)}>수정</button>
id: Todo['id'] 타입 사용투두 토글
const handleToggleTodo = async ({ id, completed }: ToggleTodo) => {
await fetch(`${BASE_URL}/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ completed: !completed }),
});
setTodos((prev) => prev.map((t) => (t.id === id ? { ...t, completed: !completed } : t)));
};
<span onClick={() => handleToggleTodo(todo)}>
{todo.completed ? '완료' : '진행중'} | {todo.title}
</span>
ToggleTodo 타입 사용id와 completed 타입만 필요한 경우의 타입TodoItems 컴포넌트 분리
<TodoItems todos={todos} onToggle={handleToggleTodo} onEdit={handleEditTodo} onDelete={handleDeleteTodo} />
const TodoItems = ({ todos, onToggle, onEdit, onDelete }: TodoItemProps) => {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<span onClick={() => onToggle(todo)}>
{todo.completed ? '완료' : '진행중'} | {todo.title}
</span>
<button onClick={() => onEdit(todo.id)}>수정</button>
<button onClick={() => onDelete(todo.id)}>삭제</button>
</li>
))}
</ul>
);
};
TodoItemProps 타입 사용