TypeScript | Utility Types

TypeScript-ის სტანდარტულ ბიბლიოთეკაში არის უტილიტები რომლებიც გვეხმარება ტიპების გარდაქმნაში. ამ პოსტში დავწერ იმ უტილიტებზე რომლებსაც ყველაზე ხშირად ვიყენებთ პროექტებში ასევე გაჩვენებთ მათ იმპლემენტაციას.

მაგალითად ავიღოთ შემდეგი კოდი რომლის მაგალითზეც გამოვიყენებთ უტილიტებს:

interface IPost {
  id: number
  title: string
  content: string
}

class Post {
  constructor(public post: IPost) {}
}

const savePost = (post: Post) => {}

const editPost = ({ title, content }: IPost) => {}

Partial

ხშირად ინტერფეისიდან არ გვჭირდება ყველა property

const editPost = ({ title, content }: IPost) => {
  const post = new Post({ title, content })
}

ამ შემთხვევაში ჩვენ გვინდა რომ მხოლოდ title და content განვაახლოთ, მაგრამ TypeScript-ი არ დაკოპმილირდება:

Property 'id' is missing in type '{ content: string; title: string; }' but required in type 'IPost'.

რადგან ინტერფეისში აღწერილი ყველა ტიპი არ არის გადაცემული (TypeScript-ში ? გარეშე ყველა property აუცილებელია)

შეგვიძლია გამოვიყენოთ Partial Generic Type Utility რომელიც ყველა ველს ინტერფეისზე ოპციონალურს გახდის.

class Post {
  constructor(public post: Partial<IPost>) {}
}

Partial-ი ინტერფეისს შემდეგნაირად გარდაქმნის

interface IPost {
  id?: number | undefined
  title?: string | undefined
  content?: string | undefined
}

(რეალურ პროექტზე create და update ოპერაციებისთვის სხვადასხვა ინტერფეისები უნდა იყოს გამოყენებული, ხშირად Type შეცდომები არასწორ არქიტექტურაზე მიუთითებს)

ასეთი generic-ის დასაწერად საჭიროა keyof-ის და extend-ის და in ოპერატორის გამოყენება.

keyof

აბრუნებს ინტერფეისის ველებს:

type IPostKeys = keyof IPost
// id | title | content

extend

ავრცელებს ინტერფეისს

type DetailedPost = IPost & { createdAt: Date }
/*
{
  id: number;
  title: string;
  content: string;
  createdAt: Date
}
*/

დავწეროთ Partial-ის იმპლემენტაცია `extend და keyof გამოყენებით:

type MyPartial<T> = {
  [P in keyof T]?: T[P]
}

MyPartial Generic ტიპის უტილიტაა რომელიც პარამეტრად იღებს ტიპს (ინტერფეის) და ანიჭებს ? რომელიც გარდაქმნის ველს ოპციონალურად

[P in keyof T]?: T[P]

კონსტრუქცია შეგვიძლია შემდეგნაირად წარმოვიდგინოთ

type MyPartial<IPost> = {
  /*id?: IPost['id']*/
  id?: number
  /*title?: IPost['title']*/
  title?: string
  /*content?: IPost['content']*/
  content?: string
}

in keyof T აბრუნებს id | title | content -> P ამ შემთხვევაში არის id (პირველი დაბრუნებული ველი) ვხდით ოპციონალურს ? და ვანიჭებთ ტიპს : T[P] როგორც JavaScript ობიექტში ისე: <T> ამ შემთხვევაში IPost-ია IPost['id'] დააბრუნებს id ველის ტიპს number-ს.

ამ ცოდნით შეგვიძლია სხვა Generic Type უტილიტებზე და მათ იმპლემენტაციაზე გადასვლა.

Required

Required უტილიტი ყველა ველს აუცილებელ ველად გარდაქმნის, მისი იმპლემენტაცია ძალიან გავს Partial-ის იმპლემენტაციას, იმ განსხვავებით რომ Partial-ში ? ველს ოპციონალურს ვხდით ხოლო readonly ველს აუცილებელ ველად გარდაქმნის.

class Post {
  constructor(public post: Required<IPost>) {}
}

იმპლემენტაცია:

type MyRequired<T> = {
  [P in keyof T]-?: T[P]
}

შეგვიძლია კოდი შემდეგნაირად გადავწეროთ:

const savePost = (post: Required<IPost>) => {}

const editPost = ({ title, content }: Partial<IPost>) => {}

create-ის დროს ყველა ველი აუცილებელი იქნება, ხოლო update-ის დროს კი ოპციონალური.

Readonly

Readonly უტილიტი არ გვაძლევს საშუალებას ობიქეტზე ველს სხვა მნიშვნელობა მივანიჭოთ.

const create = (post: Readonly<Post>) => {
  post.title = `${new Date()}-${post.tile}`
}

/*Cannot assign to 'title' because it is a read-only property.*/

იმპლემენტაცია:

type MyReadonly<T> = {
  readonly [P in keyof T]: T[P]
}

როგორც წინა შემთხვევებში ვიტერირებთ ინტერფეისის ველებზე და ვანიჭებთ readonly-ს.

Record

Record Generic-ი იღებს ორ პარამეტრს <K, T> ველებს და ტიპს რომელიც ამ ველებს მიენიჭება.

type IPostString = Record<keyof IPost, string>
// type IPostString = Record<'id' | 'title' | 'content', string>

იმპლემენტაცია:

type MyRecord<K extends string | number, T> {
    [P in K]: T
}

Pick

Pick Generic უტილიტი გარდაქმნის ინტერფეის იმ ველებად რომელიც მას პარამეტრად გადავეცით, ის იღებს ორ პარამეტრს <T, K> სადაც T ინტერფეისია (ტიპი) და K ველების გაერთიანება (union)

type PostBody = Pick<IPost, 'title' | 'content'>
/*
{
  title: string;
  content: string;
}
*/

იმპლემენტაცია:

type MyPick<T, K extends keyof T> = {
  [P in K]: T[P]
}

K extends keyof T აბრუნენს T ტიპში (ინტერფეისში) შემავალ ველებს, ანუ ერთგვარი შეზღუდვა არის რომ სხვა ველი არ გადავცეთ მაგალითად: created რომელიც ამ შემთხვევაში IPost-ის შემადგენელი ველი არ არის.

[P in K]: T[P] კონსტრუქციით განვსაზღვრავთ ველს და მის ტიპს.

ეს კონსტრუქცია შეგვიძლია ასე წარმოვიდგინოთ:

type MyPick<IPost, K extends 'id' | 'title' | 'content'> = {
    /*[P in K]: T[P]*/
    id: number;
    title: string'
}

Omit

Omit უტილიტი Pick-სგან განსხვავებით ინტერფეისიდან შლის ველს რომლის გაერთიანებასაც (union) პარამეტრად გადავცემთ

type PostBody = Omit<IPost, 'id'>
/*
{
  title: string;
  content: string;
}
*/

ამ შემთხვევაში id არ იქნება PostBody-ის ტიპზე.

იმპლემენტაცია:

type MyOmit<T, K extends string> = {
  [P in Exclude<keyof T, K>]: T[P]
}

დანარჩენი უტილიტები და ოფიციალური დოკუმენტაცია

Written on October 11, 2021