手撕ts面試題——不能不掌握的ts高級特性(三)

引言

上一篇我重點講述了 ts 的 keyof、in 以及 infer。本期將結合一道筆試題重點講述 ts 的一些其餘內置操做符 。git

本期涉及的操做符以下:github

  • Partial
  • Required
  • Readonly
  • Pick<T,K extends keyof T>
  • Record<K extends keyof any, T>
  • Exclude<T,U>
  • Extract<T,U>
  • Omit<T, K extends keyof any>

首先仍是先講述一下ts中的這些高級操做符,若是都已經掌握了,能夠直接跳到末尾的手撕筆試題。手撕筆試題typescript

Partial

Partial 將屬性變爲可選屬性。舉個栗子,iUser 這個接口 name 和 age 是必須的,可是同時又有另外一個接口 iOptionUser,接口屬性徹底同樣,只是裏面的 name 和 age 是可選的。比較笨的方法固然是手動再寫一個。bash

interface iUser {
  name: string;
  age: number;
}
interface iOptionUser {
  name?: string;
  age?: number;
}
複製代碼

其實,咱們能夠看到的是,iOptionUser 只是在屬性後添加一個?接口。咱們能夠簡單實現以下(該方法已內置)微信

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

Required

Required和Partial方法正好相反,是將屬性變成必須。方法一樣很是簡單,能夠這樣實現(該方法已內置)markdown

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

效果以下:async

Readonly

Readonly是將屬性變成只讀。方法一樣很是簡單,能夠這樣實現(該方法已內置)函數

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};
複製代碼

效果以下:oop

Pick<T,K extends keyof T>

Pick顧名思義,就是把一些屬性挑選出來。效果以下:post

你們能夠思考一下怎麼實現,官方源碼以下:

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

Record<K extends keyof any, T>

Record用於建立一個具備同類型屬性值的對象。

type Record<K extends keyof any, T> = {
    [P in K]: T;
};
複製代碼

Exclude<T,U>

從類型 T 中剔除全部能夠賦值給 U 的屬性,而後構造一個類型。主要用於聯合類型。

官方源碼以下:

type Exclude<T, U> = T extends U ? never : T;
複製代碼

Extract<T,U>

功能與 Exclude相反

type Extract<T, U> = T extends U ? T : never;
複製代碼

Omit<T, K extends keyof any>

主要用於剔除interface中的部分屬性。 好比接口iUser包含name、age、firstName、lastName、location屬性,而接口iUser2不包含location屬性,咱們可使用前面提到的Pick實現,但這樣會比較複雜,因此有了Omit 操做符。

interface iUser {
    name: string;
    age: number;
    firstName: string;
    lastName: string;
    location: string;
}
interface iUser2 {
    name: string;
    age: number;
    firstName: string;
    lastName: string;
}
複製代碼

效果以下:

Omit源碼以下:

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
複製代碼

手撕筆試題

這是一道 leetcode 的 ts筆試題,原題目略長,就不直接貼出來了,這裏簡化一下:

// 假設有一個這樣的類型:
interface initInterface {
  count: number;
  message: string;
  asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>>;
  syncMethod<T, U>(action: Action<T>): Action<U>;
}
// 在通過 Connect 函數以後,返回值類型爲

type Result {
  asyncMethod<T, U>(input: T): Action<U>;
  syncMethod<T, U>(action: T): Action<U>;
}
// 其中 Action<T> 的定義爲:
interface Action<T> {
  payload?: T
  type: string
}
// 如今要求寫出Connect的函數類型定義。

複製代碼

首先咱們須要明白這個題義,這裏是須要咱們把initInterface裏的非函數屬性去除,而且函數簽名發生了變化。

  1. 第一步:獲取函數屬性
type RemoveNonFunctionProps<T> = {
    [K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];

type FunctionProps = RemoveNonFunctionProps<initInterface>;
複製代碼

2. 將只包含函數屬性的類型Pick出來

type PickFunction<T> = Pick<T, RemoveNonFunctionProps<T>>;
type iFunctionInterface = PickFunction<initInterface>;
複製代碼

3.接下來就是函數轉換的過程,這裏須要用到我 上篇博文提到的infer。

咱們對比一下,轉換先後的函數簽名,發現只是去除了參數和返回結果的Promsie。

type asyncMethod<T, U> = (input: Promise<T>) => Promise<Action<U>>;
type transformAsyncMethod<T,U> = (input: T) => Action<U>;
複製代碼

咱們使用infer能夠這樣作

type TransformASyncMethod<T> = T extends (
  input: Promise<infer U>
) => Promise<Action<infer S>>
  ? (input: U) => Action<S>
  : never;
複製代碼

同理咱們看一下方法二,轉換先後:

type syncMethod<T, U> = (action: Action<T>) => Action<U>;
type transformSyncMethod<T, U> = (action: T) => Action<U>;
複製代碼

咱們依舊使用infer

type TransformSyncMethod<T> = T extends (
  action: Action<infer U>
) => Action<infer S>
  ? (action: U) => Action<S>
  : never;
複製代碼

因此轉換函數能夠這樣寫:

type TransformMethod<T> = T extends (
  input: Promise<infer U>
) => Promise<Action<infer S>>
  ? (input: U) => Action<S>
  : T extends (action: Action<infer U>) => Action<infer S>
  ? (action: U) => Action<S>
  : never;
複製代碼

4.整合 前三步,咱們已經有了完整的思路,如今就是把Connect類型定義整合起來。

type RemoveNonFunctionProps<T> = {
  [K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
type PickFunction<T> = Pick<T, RemoveNonFunctionProps<T>>;
type TransformMethod<T> = T extends (
  input: Promise<infer U>
) => Promise<Action<infer S>>
  ? (input: U) => Action<S>
  : T extends (action: Action<infer U>) => Action<infer S>
  ? (action: U) => Action<S>
  : never;
type ConnectAll<T> = {
  [K in keyof T]: TransformMethod<T[K]>;
};
type Connect<T> = ConnectAll<PickFunction<T>>;

複製代碼

本次就到這裏了,下面是我我的的微信公衆號。

@Author: WaterMan

相關文章
相關標籤/搜索