1. 程式人生 > 程式設計 >你可能不知道的typescript實用小技巧

你可能不知道的typescript實用小技巧

目錄
  • 前言
  • 函式過載
  • 對映型別
    • Partial,Readonly,Nullable,Required
    • Pick,Record
    • Exclude,Omit
    • ReturnType
  • 型別斷言
    • 列舉
      • 元組
        • 範型
          • infer
        • 總結

          前言

          用了很久的 typescript,用了但感覺又沒完全用。因為很多 typescript 的特性沒有被使用,檢視之前寫的程式碼滿屏的 any,這樣就容易導致很多 bug,也沒有發揮出 typescript 真正的“型別”威力。本文總結了一些使用 typescript 的小技巧,以後使用 typescript 時可以運用起來。

          廢話不多說,直接上程式碼。

          函式過載

          當希望傳 user 引數時,不傳 flag,傳 para 時,傳 flag。就可以這樣寫:

          interface User {
            name: string;
            age: number;
          }
          
          const user = {
            name: 'Jack',age: 123
          };
          
          class SomeClass {
          
            public test(para: User): number;
            public test(para: number,flag: boolean): number;
          
            public test(para: User | number,flag?: boolean): number {
              // 具體實現
              return 1;
            }
          }
          
          const someClass = new SomeClass();
          
          // ok
          someClass.test(user);
          someClass.test(123,false);
          
          // Error
          // someClass.test(123); 
          //Argument of type 'number' is not assignable to parameter of type 'User'.
          // someClass.test(user,false);
          //Argument of type '{ name: string; age: number; }' is not assignable to parameter of type 'number'.
          
          

          對映型別

          在瞭解對映型別之前,需要了解 keyof,never,typeof,in。

          keyof:keyof 取 interface 的鍵

          interface Point {
              x: number;
              y: number;
          }
          
          // type keys = "x" | "y"
          type keys = keyof Point;
          
          

          never:永遠不存在的值的型別

          官方描述:

          the never type represents the type of values that never occur.

          // 例子:進行編譯時的全面的檢查
          type Foo = string | number;
          
          function controlFlowAnalysisWithNever(foo: Foo) {
            if (typeof foo === "string") {
              // 這裡 foo 被收窄為 string 型別
            } else if (typeof foo === "number") {
              // 這裡 foo 被收窄為 number 型別
            } else {
              // foo 在這裡是 never
              const check: never = foo;
            }
          }
          
          

          使用 never 避免出現新增了聯合型別沒有對應的實現,目的就是寫出類cXwPjW型絕對安全的程式碼。

          typeof:取某個值的 type

          const a: number = 3
          
          // 相當於: const b: number = 4
          const b: typeof a = 4
          
          

          in:檢查一個物件上是否存在一個屬性

          interface A {
            x: number;
          }
          
          interface B {
            y: string;
          }
          
          function doStuff(q: A | B) {
            if ('x' in q) {
              // q: A
            } else {
              // q: B
            }
          }
          
          

          對映型別就是將一http://www.cppcns.com個型別對映成另外一個型別,簡單理解就是新型別以相同的形式去轉換舊型別的每個屬性。

          Partial,Required

          • Partial 將每個屬性轉換為可選屬性
          • Readonly 將每個屬性轉換為只讀屬性
          • Nullable 轉換為舊型別和null的聯合型別
          • Required 將每個屬性轉換為必選屬性
          type Partial<T> = {
              [P in keyof T]?: T[P];
          }
          
          type Readonly<T> = {
              readonly [P in keyof T]: T[P];
          }
          
          type Nullable<T> = { 
            [P in keyof T]: T[P] | null 
          }
          
          type Required<T> = {
            [P in keyof T]-?: T[P]
          }
          
          interface Person {
              name: string;
              age: number;
          }
          
          type PersonPartial = Partial<Person>;
          type PersonReadonly = Readonly<Person>;
          type PersonNullable = Nullable<Person>;
          
          type PersonPartial = {
              name?: string | undefined;
              age?: number | undefined;
          }
          
          type PersonReadonly = {
              readonly name: string;
              readonly age: number;
          }
          
          type PersonNullable = {
                name: string | null;
                age: number | null;
          }
          
          interface Props {
            a?: number;
            b?: string;
          }
          
          const obj: Props = { a: 5 };
          
          const obj2: Required<Props> = { a: 5 };
          // Property 'b' is missing in type '{ a: number; }' but required in type 'Required<Props>'.
          

          Pick,Record

          • Pick 選取一組屬性指定新型別
          • Record 建立一組屬性指定新型別,常用來宣告普通Object物件
          type Pick<T,K extends keyof T> = {
            [P in K]: T[P];
          }
          
          type Record<K extends keyof any,T> = {
            [P in K]: T;
          }
          
          interface Todo {
            title: string;
            description: string;
            completed: boolean;
          }
          
          type TodoPreview = Pick<Todo,"title" | "completed">;
          
          const todo: TodoPreview = {
            title: "Clean room",completed: false,};
          
          todo; // = const todo: TodoPreview
          
          
          interface PageInfo {
            title: string;
          }
          
          type Page = "home" | "about" | "contact";
          
          const nav: Record<Page,PageInfo> www.cppcns.com= {
            about: { title: "title1" },contact: { title: "title2" },home: { title: "title3" },};
          
          nav.about; // = const nav: Record
          
          

          Exclude,Omit

          • Exclude 去除交集,返回剩餘的部分
          • Omit 適用於鍵值對物件的Exclude,去除型別中包含的鍵值對
          type Exclude<T,U> = T extends U ? never : T
          type Omit = Pick<T,Exclude<keyof T,K>>
          
          // 相當於: type A = 'a'
          type A = Exclude<'x' | 'a','x' | http://www.cppcns.com'y' | 'z'>
          
          interface Todo {
            title: string;
            description: string;
            completed: boolean;
          }
          
          type TodoPreview = Omit<Todo,"description">;
          
          const todo: TodoPreview = {
            title: "a",completed: www.cppcns.comfalse,};
          
          

          ReturnType

          獲取返回值型別,一般為函式

          type ReturnType<T extends (...args: any) => any>
            = T extends (...args: any) => infer R ? R : any;
          
          declare function f1(): { a: number; b: string };
          type T1 = ReturnType<typeof f1>;
          //    type T1 = {
          //        a: number;
          //        b: string;
          //    }
          
          

          還有很多對映型別,可檢視Utility Types參考。

          型別斷言

          型別斷言用來明確的告訴 typescript 值的詳細型別,合理使用能減少我們的工作量。

          比如一個變數並沒有初始值,但是我們知道它的型別資訊(它可能是從後端返回)有什麼辦法既能正確推導型別資訊,又能正常運行了?有一種網上的推薦方式是設定初始值,然後使用 typeof 拿到型別(可能會給其他地方用)。也可以使用型別斷言可以解決這類問題:

          interface User { 
              name: string; 
              age: number; 
          }
          
          export default class someClass { 
              private user = {} as User;
          } 
          
          

          列舉

          列舉型別分為數字型別與字串型別,其中數字型別的列舉可以當標誌使用:

          enum AnimalFlags {
              None = 0,HasClaws = 1 << 0,CanFly = 1 << 1,HasClawsOrCanFly = HasClaws | CanFly 
          }
          
          interface Animal { 
              flags: AnimalFlags; 
             [key: string]: any; 
          }
          
          function printAnimalAbilities(animal: Animal) { 
              var animalFlags = animal.flags; 
              if (animalFlags & AnimalFlags.HasClaws) { 
                  console.log('animal has claws'); 
              } 
              if (animalFlags & AnimalFlags.CanFly) { 
                  console.log('animal can fly'); 
              } 
              if (animalFlags == AnimalFlags.None) { 
                  console.log('nothing'); 
              } 
          }
          
          var animal = { flags: AnimalFlags.None }; 
          printAnimalAbilities(animal); // nothing 
          animal.flags |= AnimalFlags.HasClaws; 
          printAnimalAbilities(animal); // animal has claws 
          animal.flags &= ~AnimalFlags.HasClaws; 
          printAnimalAbilities(animal); // nothing 
          animal.flags |= AnimalFlags.HasClaws | AnimalFlags.CanFly; 
          printAnimalAbilities(animal); // animal has claws,animal can fly 
          
          
          • 使用 |= 來新增一個標誌;
          • 組合使用 &= 和 ~ 來清理一個標誌;
          • | 來合併標誌。

          這個或許不常用,在 typescript 關於 types 原始碼中我們也可以看到類似的程式碼:

          你可能不知道的typescript實用小技巧

          字串型別的列舉可以維護常量:

          const enum TODO_STATUS {
            TODO = 'TODO',DONE = 'DONE',DOING = 'DOING'
          }
          
          function todos (status: TODO_STATUS): Todo[];
          
          todos(TODO_STATUS.TODO)
          
          

          元組

          表示一個已知元素數量和型別的陣列,各元素的型別不必相同。

          let x: [string,number];
          x = ['hello',10]; 
          

          在發出不固定多個請求時,可以應用:

          const requestList: any[] = [http.get<A>('http://some.1')]; // 設定為 any[] 型別 
          if (flag) { 
              requestList[1] = (http.get<B>('http://some.2')); 
          } 
          const [ { data: a },response ] = await Promise.all(requestList) as [Response<A>,Response<B>?]
          

          範型

          在定義泛型後,有兩種方式使用,一種是傳入泛型型別,另一種使用型別推斷。

          declare function fn<T>(arg: T): T; // 定義一個泛型函式 
          const fn1 = fn<string>('hello'); // 第一種方式,傳入泛型型別 
          string const fn2 = fn(1); // 第二種方式,從引數 arg 傳入的型別 number,來推斷出泛型 T 的型別是 number 
          

          一個扁平陣列結構建樹形結構例子:

          // 轉換前資料 
          const arr = [ 
          { id: 1,parentId: 0,name: 'test1'},{ id: 2,parentId: 1,name: 'test2'},{ id: 3,name: 'test3'} 
          ]; 
          // 轉化後 
          [ { id: 1,name: 'test1',childrenList: [ { id: 2,name: 'test2',childrenList: [] } ] },name: 'test3',childrenList: [] } 
          ]
          
          
          interface Item { 
              id: number; 
              parentId: number; 
              name: string; 
          }
          
          // 傳入的 options 引數中,得到 childrenKey 的型別,然後再傳給 TreeItem
          
          interface Options<T extends string> { 
              childrenKey: T; 
          } 
          type TreeItem<T extends string> = Item & { [key in T]: TreeItem<T>[] | [] }; 
          declare function listToTree<T extends string = 'children'>(list: Item[],options: Options<T>): TreeItem<T>[]; 
          listToTree(arr,{ childrenKey: 'childrenList' }).forEach(i => i.childrenList) 
          
          

          infer

          表示在 extends 條件語句中待推斷的型別變數。

          type ParamType<T> = T extends (param: infer P) => any ? P : T; 
          

          這句話的意思是:如果 T 能賦值給 (param: infer P) => any,則結果是 (param: infer P) => any 型別中的引數 P,否則返回為 T。

          interface User { 
              name: string; 
              age: number; 
          } 
          type Func = (user: User) => void 
          type Param = ParamType<Func>; // Param = User 
          type AA = ParamType<string>; // string
          

          例子:

          // [string,number] -> string | number
          type ElementOf<T> = T extends Array<infer E> ? E : never;
          
          type TTuple = [string,number];
          
          type ToUnion = ElementOf<TTuple>; // string | number
          
          
          // T1 | T2 -> T1 & T2
          type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
          
          type Result = UnionToIntersection<T1 | T2>; // T1 & T2
          
          

          總結

          typescript 關於型別限制還是非常強大的,由於文章有限,還有其他型別比如聯合型別,交叉型別等讀者可自行翻閱資料檢視。剛開始接觸範型以及其各種組合會感覺不熟練,接下來在專案中會慢慢應用,爭取將 bug 降至最低限度。

          到此這篇關於typescript實用小技巧的文章就介紹到這了,更多相關typescript實用小技巧內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!