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

2022-04-15 0 384
目录
  • 前言
  • 函数重载
  • 映射类型
    • 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 避免出现新增了联合类型没有对应的实现,目的就是写出类型绝对安全的代码。

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

          映射类型就是将一个类型映射成另外一个类型,简单理解就是新类型以相同的形式去转换旧类型的每个属性。

          Partial, Readonly, Nullable, 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> = {
            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' | 'y' | 'z'>
          
          interface Todo {
            title: string;
            description: string;
            completed: boolean;
          }
          
          type TodoPreview = Omit<Todo, "description">;
          
          const todo: TodoPreview = {
            title: "a",
            completed: false,
          };
          
          

          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, parentId: 0, name: 'test3'} 
          ]; 
          // 转化后 
          [ { id: 1, parentId: 0, name: 'test1', 
              childrenList: [ { id: 2, parentId: 1, name: 'test2', childrenList: [] } ] }, 
              { id: 3, parentId: 0, 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实用小技巧内容请搜索NICE源码以前的文章或继续浏览下面的相关文章希望大家以后多多支持NICE源码!

          免责声明:
          1、本网站所有发布的源码、软件和资料均为收集各大资源网站整理而来;仅限用于学习和研究目的,您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。 不得使用于非法商业用途,不得违反国家法律。否则后果自负!

          2、本站信息来自网络,版权争议与本站无关。一切关于该资源商业行为与www.niceym.com无关。
          如果您喜欢该程序,请支持正版源码、软件,购买注册,得到更好的正版服务。
          如有侵犯你版权的,请邮件与我们联系处理(邮箱:skknet@qq.com),本站将立即改正。

          NICE源码网 JavaScript 你可能不知道的typescript实用小技巧 https://www.niceym.com/23227.html