원티드 타입스크립트 챌린지를 듣고, 추천 받은 공부 방법 중에 exercise를 통해서 1부터 10까지 풀 수 있어야 된다고 해서, exercises를 풀면서 타입스크립트를 학습해보기로 했다.
사이트 : TypeScript Exercises
문제 5: 타입 구조를 중복하지 않고 전체 User 정보가 아닌 입력에 따라 현재 필요한 기준만 전달하기 위해서 filterUsers
함수를 수정하기. 추가적으로 난이도를 높여 filter criterias
에서 "type"을 제외해보기.
interface User {
type: 'user';
name: string;
age: number;
occupation: string;
}
interface Admin {
type: 'admin';
name: string;
age: number;
role: string;
}
export type Person = User | Admin;
export const persons: Person[] = [
{ type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep' },
{
type: 'admin',
name: 'Jane Doe',
age: 32,
role: 'Administrator'
},
{
type: 'user',
name: 'Kate Müller',
age: 23,
occupation: 'Astronaut'
},
{
type: 'admin',
name: 'Bruce Willis',
age: 64,
role: 'World saver'
},
{
type: 'user',
name: 'Wilson',
age: 23,
occupation: 'Ball'
},
{
type: 'admin',
name: 'Agent Smith',
age: 23,
role: 'Administrator'
}
];
export const isAdmin = (person: Person): person is Admin => person.type === 'admin';
export const isUser = (person: Person): person is User => person.type === 'user';
export function logPerson(person: Person) {
let additionalInformation = '';
if (isAdmin(person)) {
additionalInformation = person.role;
}
if (isUser(person)) {
additionalInformation = person.occupation;
}
console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`);
}
export function filterUsers(persons: Person[], criteria: User): User[] {
return persons.filter(isUser).filter((user) => {
const criteriaKeys = Object.keys(criteria) as (keyof User)[];
return criteriaKeys.every((fieldName) => {
return user[fieldName] === criteria[fieldName];
});
});
}
console.log('Users of age 23:');
filterUsers(
persons,
{
age: 23
}
).forEach(logPerson);
타입스크립트는 일반적으로 유형 변환을 쉽게 할 수 있는 Utility Type을 제공한다.
Partial<Type>
Type의 모든 속성이 선택사항으로 설정된 type을 구성한다. 이 utility는 주어진 type의 하위 집합을 나타내는 type을 반환한다.
예시)
interface Todo {
title: string;
description: string;
}
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return { ...todo, ...fieldsToUpdate };
}
const todo1 = {
title: 'organize desk',
description: 'clear clutter',
};
const todo2 = updateTodo(todo1, {
description: 'throw out trash',
})
console.log(todo2);
// {
// "title": "organize desk",
// "description": "throw out trash"
// }
Required<Type>
필수로 설정된 Type의 모든 속성으로 구성된 Type을 구성한다. Partial과 반대!
interface Props {
a?: number;
b?: string;
}
const obj: Props = { a: 5 };
const obj2: Required<Props> = { a: 5 };
// Property 'b' is missing in type '{ a: number; }' but required in type 'Required<Props>'.
Omit<Type, Keys>
Type에서 모든 property를 선택한 다음 Keys(문자열 또는 union 문자열)를 제거하여 type을 구성한다.
예시)
interface Todo {
title: string;
description: string;
completed: boolean;
createdAt: number;
}
type TodoPreview = Omit<Todo, 'description'>;
const todo: TodoPreview = {
title: 'clean room',
completed: false,
createdAt: 202203131614,
};
console.log(todo);
type TodoInfo = Omit<Todo, 'completed' | 'createdAt'>;
const todoInfo: TodoInfo = {
title: 'pick up kids',
description: 'place: kindergarden',
completed: true, // completed, createdAt 둘 다 없어야 된다.
}
// 에러
// Type '{ title: string; description: string; completed: boolean; }' is not assignable to type 'TodoInfo'.
// Object literal may only specify known properties, and 'completed' does not exist in type 'TodoInfo'.
Partial<Type>
을 이용해서 문제 풀기filterUsers
함수에 주어진 객체에 age 속성밖에 없기 때문에 속성을 optional하게 하기 위해 Partial<Type>
을 이용해보았다.
export function filterUsers(persons: Person[], criteria: Partial<User>): User[] {
return persons.filter(isUser).filter((user) => {
const criteriaKeys = Object.keys(criteria) as (keyof User)[];
return criteriaKeys.every((fieldName) => {
return user[fieldName] === criteria[fieldName];
});
});
}
Omit<Type, Keys>
를 이용해서 문제 풀기좀 더 난이도를 높여서 filter criterias
에서 "type"을 제외해보기 위해서 Omit<Type, Keys>
를 이용했다.
function filterUsers(persons: Person[], criteria: Partial<Omit<User, 'type'>>): User[] {
return persons.filter(isUser).filter((user) => {
const criteriaKeys = Object.keys(criteria) as (keyof Omit<User, 'type'>)[];
retirm criteriaKeys.every((fieldName) => {
return user[fieldName] === criteria[fieldName];
});
});
}
keyof
연산자는 객체 타입을 받고, key의 문자열 또는 숫자 리터럴 union을 생성한다. 간단하게 말하면 객체 타입에서 key들만 추출한 것이다.
type Todo = { title: string; completed: boolean };
type T = keyof Todo; // 'title' | 'completed'
만약 type에 string 또는 number로 index signature가 있는 경우, keyof
는 아래 예시처럼 type을 반환한다.
type Arrayish = { [n: number]: unknown};
type A = keyof Arrayish; // number type
type Mapish = { [k: string] : boolean };
type M = keyof Mapish; // string | number type
// obj["0"]은 obj[0]과 같기 때문에 number type이 나오는 것임
타입 단언은 타입 시스템에 사용자가 타입을 가장 잘 알고 있다는 것을 명시하는 방법으로 as
키워드를 사용한다. 타입 단언 사용은 최대한 하지말자...!
const criteriaKeys = Object.keys(criteria) as (keyof Omit<User, 'type'>)[];
// 과제 안에 쓰인 것으로 보았을 때 Object.keys(criteria)의 타입을 (keyof Omit<User, 'type'>)[]으로 명시한 것이다."
const a = 'name' as keyof Omit<User, 'type'> // 'occupation' | 'name' | 'age'
참고 주소