원티드 타입스크립트 챌린지를 듣고, 추천 받은 공부 방법 중에 exercise를 통해서 1부터 10까지 풀 수 있어야 된다고 해서, exercises를 풀면서 타입스크립트를 학습해보기로 했다.
사이트 : TypeScript Exercises
문제: filterPerson
함수를 수정하여, personType이 'user'인 경우 사용자를 필터링하고, User[]
를 반환하고, personType이 'admin'인 경우 Admin[]
을 반환할 수 있도록 해주세요. 또한 personType에 따라 부분적인 User, Admin type을 인식할 수 있도록 해야 된다. criteria 인자는 personType 인자 값에 따라 작동해야 한다. criteria field에 type field를 사용할 수 없습니다.
❗️ 심화 문제: getObjectKeys()
함수를 구현하여, 어떤 인자를 전달하더라도 더 편리한 결과를 반환할 수 있도록 하여 캐스팅 할 필요가 없도록 해주세요.
let criteriaKeys = Object.keys(criteria) as (keyof User)[];
--> let criteriaKeys = getObjectKeys(criteria);
문제 :
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: 'Anti-virus engineer' }
];
export function logPerson(person: Person) {
console.log(
` - ${person.name}, ${person.age}, ${person.type === 'admin' ? person.role : person.occupation}`
);
}
export function filterPersons(persons: Person[], personType: string, criteria: unknown): unknown[] {
return persons
.filter((person) => person.type === personType)
.filter((person) => {
let criteriaKeys = Object.keys(criteria) as (keyof Person)[];
return criteriaKeys.every((fieldName) => {
return person[fieldName] === criteria[fieldName];
});
});
}
export const usersOfAge23 = filterPersons(persons, 'user', { age: 23 });
export const adminsOfAge23 = filterPersons(persons, 'admin', { age: 23 });
console.log('Users of age 23:');
usersOfAge23.forEach(logPerson);
console.log();
console.log('Admins of age 23:');
adminsOfAge23.forEach(logPerson);
참고주소
overload signature
를 작성하여, 다양한 방법으로 호출할 수 있는 함수를 지정할 수 있다.
// overload signature
function filterPersons(persons: Person[], personType: 'user', criteria: Partial<User>): User[];
function filterPersons(persons: Person[], personType: 'admin', criteria: Partial<Admin>): Admin[];
// implementation signature
function filterPersons(persons: Person[], personType: string, criteria: Partial<Person>): unknown[] { ... }
2개 이상의 함수 시그니처를 작성한 후 함수의 본문을 작성한다. implementation signature는 바깥쪽에서는 볼 수 없기 때문에 오버로드 함수를 작성할 때, 항상 함수 구현 위에는 2개 이상의 시그니처가 있어야 된다.
구현 시그니처는 오버로드 시그니처랑 호환이 가능해야 된다.
// 이 예시들은 구현 시그니처와 오버로드 시그니처가 호환 가능하지 않은 상태이기 때문에 오류가 발생한다.
function fn(x: string): string;
function fn(x: number): boolean;
function fn(x: string | number) {
return 'oops';
}
function len(s: string): number;
function len(arr: any[]): number;
function len(x: any) {
return x.length;
}
len(""); // OK
len([0]); // OK
len(Math.random() > 0.5 ? 'hello': [0]);
// 문자열 또는 배열일 수 있는 값으로는 호출할 수 없다.
// 타입스크립트는 하나의 오버로드만 해결할 수 있기 때문
// 인수 개수와 반환 타입이 같이 때문에 오버로드 되지 않은 버전의 함수를 작성하여 해결
function len(x: any[] | string) {
return x.length
}
오버로드 되지 않은 버전의 함수를 작성하는 편이 훨씬 나은 바법이다. 호출자는 어떤 값으로든 이 함수를 호출할 수 있고, 올바른 구현 시그니처를 찾지 않아도 된다. 가능하면 항상 오버로드 대신 union type의 매개변수를 선호하라고 타입스크립트 공식문서에 나와 있다.
export function filterPersons(persons: Person[], personType: string, criteria: Partial<User>): User[];
export function filterPersons(persons: Person[], personType: string, criteria: Partial<Admin>): Admin[];
export function filterPersons(persons: Person[], personType: string, criteria: Partial<Person>): unknown[] {
return persons
.filter((person) => person.type === personType)
.filter((person) => {
let criteriaKeys = Object.keys(criteria) as (keyof Person)[];
return criteriaKeys.every((fieldName) => {
return person[fieldName] === criteria[fieldName];
});
});
}
typescript playground에서 풀었을 때는 에러가 발생하지 않기에 문제를 해결한 줄 알았다. 하지만 typescript exercise에서 똑같이 해결하려고 했을 때는 test.ts(29,5): error TS2344: Type 'false' does not satisfy the constraint 'true'.
에러가 발생했다.
test.ts 파일을 확인해보았다.
filtered3에서 문제가 발생한 것이었다.
문제를 다시 읽어본 결과, personType이 'user'일 때, personType이 'admin'일 때... 라고 적힌 것을 보고 아래와 같이 수정하였더니 통과했다.
export function filterPersons(persons: Person[], personType: 'user', criteria: Partial<User>): User[];
export function filterPersons(persons: Person[], personType: 'admin', criteria: Partial<Admin>): Admin[];
export function filterPersons(persons: Person[], personType: string, criteria: Partial<Person>): unknown[] {
return persons
.filter((person) => person.type === personType)
.filter((person) => {
let criteriaKeys = Object.keys(criteria) as (keyof Person)[];
return criteriaKeys.every((fieldName) => {
return person[fieldName] === criteria[fieldName];
});
});
}
심화문제는 풀어보려고 해봤는데, 아직 나에게는 무리인가보다.
function getObejctKeys(criteria: Partial<Person>): string[] {
return Object.keys(criteria) as (keyof Person)[];
};
function filterPersons(persons: Person[], personType: 'user', criteria: Partial<User>): User[];
function filterPersons(persons: Person[], personType: 'admin', criteria: Partial<Admin>): Admin[];
function filterPersons(persons: Person[], personType: string, criteria: Partial<Person>): unknown[] {
return persons
.filter((person) => person.type === personType)
.filter((person) => {
let criteriaKeys = getObejctKeys(criteria)
return criteriaKeys.every((fieldName) => {
return person[fieldName] === criteria[fieldName];
});
});
}
이렇게 getObjectKeys 함수를 만들어줬는데... fieldName 부분에서 에러가 발생한다.
조금 더 공부한 다음에 심화 문제 다시 풀어서 글 수정해야 되겠다.