오늘은 어제 예고한대로 Generic과 Utility type에 대해 정리해보려고 한다.
1. Generic 제네릭
제네릭은 지난번 게시글에서 잠깐 보고 지나쳤다.
let oneToSeven: number[] = [1, 2, 3, 4, 5, 6, 7];
let oneToSeven: Array<number> = [1, 2, 3, 4, 5, 6, 7];
let array: (string | number)[] = ["Apple", 1, 2, "Banana", "Mango", 3];
let array: Array<string | number> = ["Apple", 1, 2, "Banana", "Mango", 3];
이처럼 `<>`내부에 작성하는 방법을 generic이라고 한다. 이렇게 작성하면 마치 클래스나 함수에 parameter를 입력하는 것처럼 타입을 사용할 수 있다.
function printAnything<T>(arr: T[]): void {
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
}
printAnything(['a', 'b', 'c']); // <string>을 써주지 않아도 타입 추론이 가능
printAnything([1, 2, 3]); // <number>를 써주지 않아도 타입 추론이 가능
printAnything<string>(['a','b','c']) // string으로 명시
React hook에도 generic을 사용할 수 있다.
import { useState } from "react";
function App() {
const [counter, setCounter] = useState<number>(1);
const increment = () => {
setCounter((prev) => prev++);
};
return <div onClick={increment}>{counter}</div>;
}
export default App;
Generic도 타입 추론을 하기 때문에 JS에서 하던 것처럼 `useState(1)`이라고 해도 된다.
2. Utility type 유틸리티 타입
유틸리티 타입은 generic을 사용해서 타입을 효율적으로 쓸 수 있게 해준다.
`Pick<T,K>`
`T`에서 프로퍼티 `K`의 집합을 선택해 타입을 구성함. 이때 `K`는 `T`의 부분 집합이어야 한다.
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, 'title' | 'completed'>;
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
};
`Omit<T,K>`
`T`에서 `K`를 제외한 모든 프로퍼티로 이루어진 타입을 구성함. `K`에는 `T`에 없는 프로퍼티가 있어도 된다.
위 예시에서 `Todo`에서 `description`을 제외하면 `TodoPreview`타입과 동일함.
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview2 = Omit<Todo, 'description'>;
const todo: TodoPreview2 = {
title: 'Clean room',
completed: false,
};
`Excluce<T,U>`
`T`에서 `U`에 할당할 수 있는 모든 속성을 제외한 타입을 구성함. 이때 `U`에는 `T`에 없는 프로퍼티가 있어도 된다.
type T0 = Exclude<"a" | "b" | "c", "a" | "d">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number
`Extract<T,U>`
`Exclude`와 반대로 `T`에서 `U`에 할당할 수 있는 프로퍼티를 모두 추출해 타입을 구성함.
type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T1 = Extract<string | number | (() => void), Function>; // () => void
`Partial<T>`
`T`의 모든 프로퍼티를 갖지 않아도 되는 타입을 구성함.
interface Todo {
title: string;
description: string;
}
function update(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return { ...todo, ...fieldsToUpdate };
}
const todo1 = {
title: 'organize desk',
description: 'clear clutter',
};
const todo2 = update(todo1, {
description: 'throw out trash',
});
console.log(todo1) //{ title: 'organize desk', description: 'clear clutter' }
console.log(todo2) //{ title: 'organize desk', description: 'throw out trash' }
`Readonly<T>`
`T`의 모든 프로퍼티를 읽기 전용(read only)으로 설정한 타입을 구성함. 해당 프로퍼티는 재할당이 불가능해진다.
interface Todo {
title: string;
}
const todo: Readonly<Todo> = {
title: 'Delete inactive users',
};
todo.title = 'Hello'; // 오류: 읽기 전용 프로퍼티에 재할당할 수 없음
` Parameters<T>`와 ` ReturnType<T>`
각각 함수의 parameter와 리턴값의 타입을 정해준다. `Parameters<T>`는 `T`의 파라미터를 배열로 반환해주고, `ReturnType<T>`는 parameter 괄호 옆에 작성해주면 된다.
function log(message: string, userId: number): void {
console.log(`${userId}: ${message}`);
}
type LogParams = Parameters<typeof log>; // [string, number]
const params: LogParams = ['Hello, world!', 1];
log(...params); // 1: Hello, world!
type LogReturnType = ReturnType<typeof log>; // void
여기서 `ReturnType`으로 설정된 `void`는 `log` 함수가 반환하는 값이 없기 때문에 주어진 것이다.
` Awaited<T>`
`Promise`의 결과 타입을 추론하는 유틸리티 타입이다. 비동기 함수의 반환 타입을 처리하거나, `Promise`로 감싸진 값을 추출할 때 유용하다.
async function fetchData(): Promise<string> {
return "Hello, world!";
}
// fetchData 함수의 반환 타입 추론
type FetchDataType = Awaited<ReturnType<typeof fetchData>>;
const data: FetchDataType = await fetchData();
console.log(data); // "Hello, world!"
이외에도 위 예시에 나온 `Promise<T>`와 `Record<T>` 등이 있으니 다른 유틸리티 타입은 공식 문서를 참고하자.
사용하다보니 헷갈리는 경우가 많아서 사용 예시를 몇 개 가져왔다.
function add({ a = 1, b = 2 }: { a: number; b: number }): number {
return a + b;
}
export type Todo = {
id: string;
title: string;
completed: boolean;
};
export type Paginate<T> = {
data: T[];
first: number;
items: number;
last: number;
next: number | null;
pages: number;
prev: number | null;
};
// export async function getTodos (): Promise<Todo[]> {
export async function getTodos() {
const res = await fetch("http://localhost:4000/todos?_page=1&_per_page=25");
// const data: Todo[] = await res.json();
const data: Paginate<Todo> = await res.json();
return data;
}
// function TodoList({ todoList }: { todoList: Todo[] }) {
type TodoListProps = {
todoList: Todo[];
onDeleteClick: (id: Todo["id"]) => void; // Todo['id'] : Todo의 'id'의 타입을 가져오기
onToggleClick: (toggleTodo: ToggleTodo) => void;
};
function TodoList({ todoList, onDeleteClick, onToggleClick }: TodoListProps) {
return (
<>
{todoList.map((todo) => (
<TodoItem
{...todo}
onDeleteClick={onDeleteClick}
onToggleClick={onToggleClick}
key={todo.id}
/>
))}
</>
);
}
'Frontend > Today I Learned' 카테고리의 다른 글
[Next.js] TanStack Query로 count up/down 기능 구현하기 (2) | 2024.10.01 |
---|---|
[Next.js] React Server Component & Client Component (1) | 2024.09.30 |
[TS] TypeScript 시작하기 (4) | 2024.09.24 |
[CSS] position : fixed, sticky와 width:inherit (1) | 2024.09.02 |
[JS, supabase] 데이터 저장하고 불러오기 (0) | 2024.08.30 |