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]
}