天天看點

深入typescript之‘可選的’和‘必要的’

随着前端項目的規模不斷變大,多人協同開發越來越受到所有公司的歡迎。随之而來的就是 TypeScript 被越來越多的項目所使用,這種變化并不是對技術的盲目追求,而是業務驅動下的技術進步,TypeScript 通過對原生 JavaScript 提供強類型加持,在很大程度上提升了代碼品質,大大降低了多人協同場景下不同子產品接口互相調用可能出現的隐性 bug。

比如:如果一個接口的某個屬性是非必要的,那麼你可以使用可選​

​?:​

​,但是

  1. 如果一個接口的屬性在有的繼承中是必要的,而在另一些時候是非必要的呢?
  2. 如果一個接口中的某一個/些屬性是不能要的呢?

Partial:可選

1 從源碼看,它是什麼

​Partial<Type>​

​類型的源碼如下所示:

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

這裡需要關注四個點:

  • ​<T>​

    ​:這是目标類型,也就是我們要做處理的類型,類型不确定,是以用泛型 T 表示
  • ​[P in keyof T] :keyof T​

    ​傳回 T 類型的所有鍵組成的一個類型,in 可以按照js中的for…in周遊去了解,後續對keyof有更詳細的說明
  • ​?:​

    ​可選,把傳回類型的所有屬性都轉為可選類型
  • 傳回的是一個新類型,這個新類型來源于 T,并且和 T 在屬性上有一種繼承關系!

基于對源碼的了解,就可以很好的了解​

​Partial<Type>​

​類型的作用就是傳回一個新類型,這個新類型和目标類型 T 擁有相同的屬性,但所有屬性都是可選的。

2 從實戰看,它怎麼用

場景說明:在實際的業務開發中,經常會遇到需要對一個資料對象做整體或者局部更新的需求,這裡就可以用到​

​Partial<Type>​

interface DataModel {
    name: string
    age: number
    address: string
}

let store: DataModel = {
    name: '',
    age: 0,
    address: ''
}

function updateStore (
    store: DataModel,
    payload: Partial<DataModel>
):DataModel {
    return {
        ...store,
        ...payload
    }
}

store = updateStore(store, {
    name: 'lpp',
    age: 18
})      

3 補充

之前曾經專門講過 keyof 修飾符 —— 它傳回一個聯合類型。這裡增加一個對 keyof 的說明,通過一段代碼來了解一下:

interface Person {
    name: string;
    age: number;
    location: string;
}

type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[];  // "length" | "push" | "pop" | "concat" | ...
type K3 = keyof { [x: string]: Person };  // string

const person: Person = {
    name: '',
    age: 0,
    location: ''
}

type k11 = keyof typeof person; // "name" | "age" | "location"      

Required :必要的

1 從源碼看,它是什麼

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

這個類型的作用就是把類型 T 中的所有屬性都轉為可選屬性。

這裡源碼中使用了一個 ​

​-?​

​​ 來标注屬性為必填的屬性,那麼這個 ​

​-?​

​​ 是否是必須的呢?因為我們了解的可選屬性是用 ​

​?​

​ 明确辨別的才是可選的。

如果我們把 ​

​-?​

​​ 去掉,為什麼就無法實作 Required 的效果了呢?我們先自己實作一個 ​

​MyRequired<T>​

​,如下所示:

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

interface Props {
    a?: number;
    b?: string;
}

const obj: MyRequired<Props> = { 
    a: 5
};      

上面的代碼是沒有類型錯誤的,因為如果隻是​

​[P in keyof T]​

​​ ,P 中的屬性會保留它自身在 T 中的可選性。即之前如果是必填的,在新類型中還是必填的,如果是選填的同理。有點類似一種“繼承關系”。是以使用 ​

​-?​

​ 來清除可選性實作 Required 。

2 從實戰看,它怎麼用

​Required<T>​

​會将傳入的T類型的所有屬性都轉為必要的。是以最常見的用法就是做諸如此類的轉換,但是如果隻是想把 T 類型的某些屬性轉為必填并把這些屬性傳回成一個新類型?我們可以這麼做:

interface Props {
    a?: string
    b?: string
    c?: string
}

// 僅保留b,c屬性并轉為必填
type NewProps1 = Required<Pick<Props, 'b' | 'c'>>

// 需要保留Props的所有屬性,但是b,c需要必填
type NewProps2 = Partial<Props> & Required<Pick<Props, 'b' | 'c'>>

const obj: NewProps2 = {
    b: '1',
    c: '2'
}      

Omit<Type, Keys>:忽略

構造一個類型,這個類型包含類型 Type中除了 Keys 之外的其餘屬性。 Keys是一個字元串或者字元串并集。

1 從源碼看,它是什麼

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
type Exclude<T, U> = T extends U ? never : T;      

因為 Omit 依賴了 Exclude ,是以這裡把 Exclude 的類型源碼一起貼在這裡。​

​Exclude<T, U>​

​ 的作用是從 T 中排除那些可以配置設定給 U 的類型。

Exclude 的實作原理也很簡單:在typescript中,never修飾的屬性永遠“不可到達”。但這裡隻需要知道功能即可。

有人疑惑于Exclude, T似乎比U的範圍大一些 ? 也正是因為這樣,是以T中的屬性總有不是從U中extends過來的, 我們需要的也是這些屬性 !

是以可以把 ​

​Exclude<keyof T, K>​

​ 看作是一個反選,選出了 T 中那些不包含在 K 中的屬性,然後在用 Pick ,就實作了 Omit 。

2 實戰用法

interface Person {
    name: string
    age: number
    id: string
    work: string
    address: string
    girlFriend: number
}

// 沒女朋友的人
type PersonNoGirlFriend =Omit<Person, 'girlFriend'>
//相當于:
type PersonNoGirlFriend =Pick<Person, 'name' | 'age' | 'id' | 'work' | 'address'>      

源碼和代碼中我們可以看到一個新的類型 —— Pick。它的意思是“挑取”。

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

繼續閱讀