타입스크립트 연습하기
https://typescript-exercises.github.io/#exercise=1&file=%2Findex.ts
1단계 exercise이다.
users라는 배열이 있고 이에 맞는 User라는 인터페이스 타입을 만들어 지정해주었다.
export interface User {
name: string;
age: number;
occupation: string;
};
export const users: User[] = [
{
name: 'Max Mustermann',
age: 25,
occupation: 'Chimney sweep'
},
{
name: 'Kate Müller',
age: 23,
occupation: 'Astronaut'
}
];
export function logPerson(user: User) {
console.log(` - ${user.name}, ${user.age}`);
}
console.log('Users:');
users.forEach(logPerson);
처음에 지정해주었던 User 외에도 배열의 타입을 타입을 확장해야 할 필요가 생겼다. 직업이 아니라 역할을 데이터로 갖는 person 객체가 생겼기 때문. 이때 User 인터페이스를 수정할 수도 있겠지만 다른 형태에 대한 인터페이스를 만들고 aliase를 통해 타입을 확장하면서도 엄격하게 지킬 수 있도록 할 수 있다.
interface User {
name: string;
age: number;
occupation: string;
}
interface Admin {
name: string;
age: number;
role: string;
}
export type Person = User | Admin;
export const persons: Person[] /* <- Person[] */ = [
{
name: 'Max Mustermann',
age: 25,
occupation: 'Chimney sweep'
},
{
name: 'Jane Doe',
age: 32,
role: 'Administrator'
},
{
name: 'Kate Müller',
age: 23,
occupation: 'Astronaut'
},
{
name: 'Bruce Willis',
age: 64,
role: 'World saver'
}
];
export function logPerson(user: Person) {
console.log(` - ${user.name}, ${user.age}`);
}
persons.forEach(logPerson);
기존에 자바스크립트를 통해 작업했던 것을 타입스크립트로 바꾸는 작업을 통해 타입스크립트를 처음 사용했었다. 뭔가 잘못된 느낌이 들면서도 그냥 ? 이거 하나 붙여서 타입을 계속 바꿨었는데 이런 방식이 있구나 벌써 깨달음을 얻었다!
in
operator로 타입 좁히기"role" in person
이 부분이다.interface User {
name: string;
age: number;
occupation: string;
}
interface Admin {
name: string;
age: number;
role: string;
}
export type Person = User | Admin;
export const persons: Person[] = [
{
name: 'Max Mustermann',
age: 25,
occupation: 'Chimney sweep'
},
{
name: 'Jane Doe',
age: 32,
role: 'Administrator'
},
{
name: 'Kate Müller',
age: 23,
occupation: 'Astronaut'
},
{
name: 'Bruce Willis',
age: 64,
role: 'World saver'
}
];
export function logPerson(person: Person) {
let additionalInformation: string;
if ("role" in person) {
additionalInformation = person.role;
} else {
additionalInformation = person.occupation;
}
console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`);
}
persons.forEach(logPerson);
parameterName is Type
isAdmin, isUser함수가 리턴하는 값에 타입을 지정해준다.
리턴값에 타입이 지정되지 않으면 logPerson 함수 내에서 isAdmin(person)이 true라도 additionalInformation에 person.role을 할당하지 못한다. role이 없다고 하더라.
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' }
];
export function isAdmin(person: Person): person is Admin {
return person.type === 'admin';
}
export function isUser(person: Person): person is User {
return person.type === 'user';
}
export function logPerson(person: Person) {
let additionalInformation: string = '';
if (isAdmin(person)) {
additionalInformation = person.role;
}
if (isUser(person)) {
additionalInformation = person.occupation;
}
console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`);
}
console.log('Admins:');
persons.filter(isAdmin).forEach(logPerson);
console.log();
console.log('Users:');
persons.filter(isUser).forEach(logPerson);
내 답은 Partial<User>
인데, 레퍼런스를 보니 여기에 더해 Omit을 사용했다. Partial<Omit<User, 'type'>>
Partial<Type>
타입의 속성들을 옵션화한다. 찾아보니
부분집합
을 만족하는 타입을 정의한다고 함. 아래와 같이 간주되는 듯 하다.interface User { type?: 'user'; name?: string; age?: number; occupation?: string; }
Omit<Type, Keys>
인터페이스의 특정 키가 생략된다.
person: Omit<User, 'type'> interface User { name: string; age: number; occupation: string; }
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}`);
}
// // ------------ my answer --------------------------
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];
});
});
}
// ------------ reference --------------------------
export 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'>)[];
return criteriaKeys.every((fieldName) => {
return user[fieldName] === criteria[fieldName];
});
});
}
// ------------ reference --------------------------
console.log('Users of age 23:');
filterUsers(
persons,
{
age: 23
}
).forEach(logPerson);
export interface RouterItem {
path: string;
element: React.ReactNode;
withAuth: boolean;
label: string;
}
type SidebarItem = Omit<RouterItem, "withAuth" | "element">;