[Typescript] exercise 5

박세진·2023년 3월 13일
0

원티드 타입스크립트 챌린지를 듣고, 추천 받은 공부 방법 중에 exercise를 통해서 1부터 10까지 풀 수 있어야 된다고 해서, exercises를 풀면서 타입스크립트를 학습해보기로 했다.

사이트 : TypeScript Exercises

exercise 5

문제 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 Types

타입스크립트는 일반적으로 유형 변환을 쉽게 할 수 있는 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 Type 연산자

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

타입 단언은 타입 시스템에 사용자가 타입을 가장 잘 알고 있다는 것을 명시하는 방법으로 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'

참고 주소

profile
경험한 것을 기록

0개의 댓글