TypeScript高級的用法Partial、Required、Readonly

來源 | https://segmentfault.com/a/1190000040663155
如何讓一個(gè)類的屬性全部可選?
type User = {username: string;gender: 'male' | 'female';age: number;bio: string;password: string;}
User 類型要求所有屬性都必須有值,所以:
const user: User = {username: 'awesomeuser',};
是不可行的,會(huì)提示:
類型“{ username: string; }”缺少類型“User”中的以下屬性: gender, age, bio
如何讓它可行?使用 Partial 即可:
const user: Partial<User> = {username: 'awesomeuser'}
Partial 內(nèi)置內(nèi)型的作用就是讓一個(gè)已定義了的類型的所有屬性變得可選,具體實(shí)現(xiàn)如下:
/*** Make all properties in T optional*/type Partial<T> = {[P in keyof T]?: T[P];};
如何讓一個(gè)類型的屬性全部必填?
假設(shè)我們有下面這樣一個(gè)類型定義:
type User = {username: string;age?: number;gender?: 'male' | 'female'bio?: string;password?: string;}
然后從服務(wù)器后端拿到了一系列用戶數(shù)據(jù)的結(jié)果:
const users: User[] = await api.users.list();
此時(shí)我們嘗試去訪問用戶的 age 進(jìn)行比較,想要取出 age > 18 的用戶:
const filteredUsers = users.filter(user => user.age > 18);
此時(shí)你的 IDE 是不是提示你:對象可能為“未定義”。?為什么?因?yàn)槲覀兌x了 age 本身就是可選的屬性,未定義該屬性時(shí),它的值就是 undefined,而在 typescript 中, undefined 是不允許與 number 類型進(jìn)行比較的。
但是此時(shí)其實(shí)我們是能很確定 api.users.list 接口返回給我的,要么是拋出錯(cuò)誤,要么給到我的就一定是帶了 age 值的 User 類型數(shù)據(jù),所以,我是完全可以去比較的,但是由于 TypeScript 并不知道你加載的數(shù)據(jù)是 User 還是所有屬性都已經(jīng)有值的特殊的 User,那怎么辦?什么 Required 即可。
const users: Required<User>[] = [];
或者讓 api.users.list() 的返回類型就是 Required<User>[] 即可。
如何自己實(shí)現(xiàn) Required:
/*** Make all properties in T required*/type Required<T> = {[P in keyof T]-?: T[P];};
如何讓一個(gè)類型的所有屬性變成只讀?
假設(shè)我們現(xiàn)在在寫一個(gè)系統(tǒng),當(dāng)用戶登錄之后,會(huì)共享一個(gè) User 對象給所有組件,但是只允許他人讀取其值,不允許他人修改,但是我們定義的 User 是整個(gè)系統(tǒng)共用的,有編輯功能的頁面也在使用這個(gè)類型定義,它們是需要能修改用戶數(shù)據(jù)的,那怎么辦?
使用 Readonly 即可:
const user = {username: "awesomeuser",gender: "male",age: 19,bio: "Aha, insight~",};const sharedUser = user as Readonly<User>;sharedUser.username = "new Name";
此時(shí),IDE 就會(huì)告訴你無法分配到 "username" ,因?yàn)樗侵蛔x屬性。
注意:TypeScript 只作靜態(tài)類型校驗(yàn),但是并不表示這些值真的不能被修改了,如果真的要?jiǎng)?chuàng)建一個(gè)真正從程序上都不能被修改的對象,請問怎么解決?
我想有一個(gè)類,只具有另一個(gè)類的部分屬性定義
還是那個(gè) User,我想定義一個(gè) Login 類型,它專門用于登錄時(shí)的類型,但是我又不想重新去寫一遍 username 與 password 的類型定義(雖然在本例中,重新寫一遍也很簡單,但保不齊哪天就會(huì)遇到一個(gè)十分復(fù)雜的類型定義呢?),怎么辦?使用 Pick 即可。
type User = {username: string;gender?: "male" | "female";age?: number;bio?: string;password?: string;};type Login = Pick<User, "username" | "password">;const login: Login = {username: "pantao",};
你們也看到了,Login 類型還是只有 username 是必填的,password 是可選的,這與我們的要求不符,怎么辦?加上 Required 啊。
type User = {username: string;gender?: "male" | "female";age?: number;bio?: string;password?: string;};type Login = Required<Pick<User, "username" | "password">>;const login: Login = {username: "pantao",};
此時(shí)就會(huì)要求你必須輸入 password 了。
如何自己實(shí)現(xiàn) Pick?
/*** From T, pick a set of properties whose keys are in the union K*/type Pick<T, K extends keyof T> = {[P in K]: T[P];};
如果快速定義一個(gè)類型中,具有相同數(shù)據(jù)類型的屬性?
假設(shè)我們現(xiàn)在在開發(fā)一個(gè)商城系統(tǒng),每一個(gè) Store 都有一個(gè)店長,一個(gè)倉庫管理員,一個(gè)收銀員,他們都關(guān)聯(lián)到了 User 上面,此時(shí)你可以這樣定義 Store 這個(gè)類型:
type User = {username: string;gender?: "male" | "female";age?: number;bio?: string;password?: string;};type Store = {name: string;location: string;leader: User;warehouseKeeper: User;cashier: User;};
當(dāng)然,你還可以這樣定義:
type Store = Record<'leader' | 'warehouseKeeper' | 'cashier', User> & {name: string;location: string;}
兩種方式,哪種更好,當(dāng)然各有優(yōu)劣,但是假設(shè)我們遇到下面這樣的一個(gè)情況:
type User = {username: string;gender?: "male" | "female";age?: number;bio?: string;password?: string;};const users: User = [{username: "awesomeuser",},{username: "baduser",},{username: "gooduser",},];
為了訪問方便,我想將 users 變量轉(zhuǎn)換成一個(gè)屬性名稱為 users 數(shù)組索引,值為 users 數(shù)組元素(即 User 類型)的對象,怎么定義這樣的一個(gè)類型?
你可以這樣:
type UserSet = { [key: number]: User };const userSet: UserSet = users.reduce((set, user, index) => ({ ...set, [index]: user }),{} as UserSet);
你也可以這樣:
type UserSet = Record<number, User>;
如何自己實(shí)現(xiàn) Record?
/*** Construct a type with a set of properties K of type T*/type Record<K extends keyof any, T> = {[P in K]: T;};
本文完~
學(xué)習(xí)更多技能
請點(diǎn)擊下方公眾號
![]()

