经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » TypeScript » 查看文章
你可能不知道的typescript实用小技巧
来源:jb51  时间:2021/8/26 12:37:42  对本文有异议

前言

用了很久的 typescript,用了但感觉又没完全用。因为很多 typescript 的特性没有被使用,查看之前写的代码满屏的 any,这样就容易导致很多 bug,也没有发挥出 typescript 真正的“类型”威力。本文总结了一些使用 typescript 的小技巧,以后使用 typescript 时可以运用起来。

废话不多说,直接上代码。

函数重载

当希望传 user 参数时,不传 flag,传 para 时,传 flag。就可以这样写:

  1. interface User {
  2. name: string;
  3. age: number;
  4. }
  5.  
  6. const user = {
  7. name: 'Jack',
  8. age: 123
  9. };
  10.  
  11. class SomeClass {
  12.  
  13. public test(para: User): number;
  14. public test(para: number, flag: boolean): number;
  15.  
  16. public test(para: User | number, flag?: boolean): number {
  17. // 具体实现
  18. return 1;
  19. }
  20. }
  21.  
  22. const someClass = new SomeClass();
  23.  
  24. // ok
  25. someClass.test(user);
  26. someClass.test(123, false);
  27.  
  28. // Error
  29. // someClass.test(123);
  30. //Argument of type 'number' is not assignable to parameter of type 'User'.
  31. // someClass.test(user, false);
  32. //Argument of type '{ name: string; age: number; }' is not assignable to parameter of type 'number'.
  33.  

映射类型

在了解映射类型之前,需要了解 keyof, never, typeof, in。

keyof:keyof 取 interface 的键

  1. interface Point {
  2. x: number;
  3. y: number;
  4. }
  5.  
  6. // type keys = "x" | "y"
  7. type keys = keyof Point;
  8.  

never:永远不存在的值的类型

官方描述:

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

  1. // 例子:进行编译时的全面的检查
  2. type Foo = string | number;
  3.  
  4. function controlFlowAnalysisWithNever(foo: Foo) {
  5. if (typeof foo === "string") {
  6. // 这里 foo 被收窄为 string 类型
  7. } else if (typeof foo === "number") {
  8. // 这里 foo 被收窄为 number 类型
  9. } else {
  10. // foo 在这里是 never
  11. const check: never = foo;
  12. }
  13. }
  14.  

使用 never 避免出现新增了联合类型没有对应的实现,目的就是写出类型绝对安全的代码。

typeof:取某个值的 type

  1. const a: number = 3
  2.  
  3. // 相当于: const b: number = 4
  4. const b: typeof a = 4
  5.  

in:检查一个对象上是否存在一个属性

  1. interface A {
  2. x: number;
  3. }
  4.  
  5. interface B {
  6. y: string;
  7. }
  8.  
  9. function doStuff(q: A | B) {
  10. if ('x' in q) {
  11. // q: A
  12. } else {
  13. // q: B
  14. }
  15. }
  16.  

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

Partial, Readonly, Nullable, Required

  • Partial 将每个属性转换为可选属性
  • Readonly 将每个属性转换为只读属性
  • Nullable 转换为旧类型和null的联合类型
  • Required 将每个属性转换为必选属性
  1. type Partial<T> = {
  2. [P in keyof T]?: T[P];
  3. }
  4.  
  5. type Readonly<T> = {
  6. readonly [P in keyof T]: T[P];
  7. }
  8.  
  9. type Nullable<T> = {
  10. [P in keyof T]: T[P] | null
  11. }
  12.  
  13. type Required<T> = {
  14. [P in keyof T]-?: T[P]
  15. }
  16.  
  17. interface Person {
  18. name: string;
  19. age: number;
  20. }
  21.  
  22. type PersonPartial = Partial<Person>;
  23. type PersonReadonly = Readonly<Person>;
  24. type PersonNullable = Nullable<Person>;
  25.  
  26. type PersonPartial = {
  27. name?: string | undefined;
  28. age?: number | undefined;
  29. }
  30.  
  31. type PersonReadonly = {
  32. readonly name: string;
  33. readonly age: number;
  34. }
  35.  
  36. type PersonNullable = {
  37. name: string | null;
  38. age: number | null;
  39. }
  40.  
  41. interface Props {
  42. a?: number;
  43. b?: string;
  44. }
  45.  
  46. const obj: Props = { a: 5 };
  47.  
  48. const obj2: Required<Props> = { a: 5 };
  49. // Property 'b' is missing in type '{ a: number; }' but required in type 'Required<Props>'.

Pick, Record

  • Pick 选取一组属性指定新类型
  • Record 创建一组属性指定新类型,常用来声明普通Object对象
  1. type Pick<T, K extends keyof T> = {
  2. [P in K]: T[P];
  3. }
  4.  
  5. type Record<K extends keyof any, T> = {
  6. [P in K]: T;
  7. }
  8.  
  9. interface Todo {
  10. title: string;
  11. description: string;
  12. completed: boolean;
  13. }
  14.  
  15. type TodoPreview = Pick<Todo, "title" | "completed">;
  16.  
  17. const todo: TodoPreview = {
  18. title: "Clean room",
  19. completed: false,
  20. };
  21.  
  22. todo; // = const todo: TodoPreview
  23.  
  24.  
  25. interface PageInfo {
  26. title: string;
  27. }
  28.  
  29. type Page = "home" | "about" | "contact";
  30.  
  31. const nav: Record<Page, PageInfo> = {
  32. about: { title: "title1" },
  33. contact: { title: "title2" },
  34. home: { title: "title3" },
  35. };
  36.  
  37. nav.about; // = const nav: Record
  38.  

Exclude, Omit

  • Exclude 去除交集,返回剩余的部分
  • Omit 适用于键值对对象的Exclude,去除类型中包含的键值对
  1. type Exclude<T, U> = T extends U ? never : T
  2. type Omit = Pick<T, Exclude<keyof T, K>>
  3.  
  4. // 相当于: type A = 'a'
  5. type A = Exclude<'x' | 'a', 'x' | 'y' | 'z'>
  6.  
  7. interface Todo {
  8. title: string;
  9. description: string;
  10. completed: boolean;
  11. }
  12.  
  13. type TodoPreview = Omit<Todo, "description">;
  14.  
  15. const todo: TodoPreview = {
  16. title: "a",
  17. completed: false,
  18. };
  19.  

ReturnType

获取返回值类型,一般为函数

  1. type ReturnType<T extends (...args: any) => any>
  2. = T extends (...args: any) => infer R ? R : any;
  3.  
  4. declare function f1(): { a: number; b: string };
  5. type T1 = ReturnType<typeof f1>;
  6. // type T1 = {
  7. // a: number;
  8. // b: string;
  9. // }
  10.  

还有很多映射类型,可查看Utility Types参考。

类型断言

类型断言用来明确的告诉 typescript 值的详细类型,合理使用能减少我们的工作量。

比如一个变量并没有初始值,但是我们知道它的类型信息(它可能是从后端返回)有什么办法既能正确推导类型信息,又能正常运行了?有一种网上的推荐方式是设置初始值,然后使用 typeof 拿到类型(可能会给其他地方用)。也可以使用类型断言可以解决这类问题:

  1. interface User {
  2. name: string;
  3. age: number;
  4. }
  5.  
  6. export default class someClass {
  7. private user = {} as User;
  8. }
  9.  

枚举

枚举类型分为数字类型与字符串类型,其中数字类型的枚举可以当标志使用:

  1. enum AnimalFlags {
  2. None = 0,
  3. HasClaws = 1 << 0,
  4. CanFly = 1 << 1,
  5. HasClawsOrCanFly = HasClaws | CanFly
  6. }
  7.  
  8. interface Animal {
  9. flags: AnimalFlags;
  10. [key: string]: any;
  11. }
  12.  
  13. function printAnimalAbilities(animal: Animal) {
  14. var animalFlags = animal.flags;
  15. if (animalFlags & AnimalFlags.HasClaws) {
  16. console.log('animal has claws');
  17. }
  18. if (animalFlags & AnimalFlags.CanFly) {
  19. console.log('animal can fly');
  20. }
  21. if (animalFlags == AnimalFlags.None) {
  22. console.log('nothing');
  23. }
  24. }
  25.  
  26. var animal = { flags: AnimalFlags.None };
  27. printAnimalAbilities(animal); // nothing
  28. animal.flags |= AnimalFlags.HasClaws;
  29. printAnimalAbilities(animal); // animal has claws
  30. animal.flags &= ~AnimalFlags.HasClaws;
  31. printAnimalAbilities(animal); // nothing
  32. animal.flags |= AnimalFlags.HasClaws | AnimalFlags.CanFly;
  33. printAnimalAbilities(animal); // animal has claws, animal can fly
  34.  
  • 使用 |= 来添加一个标志;
  • 组合使用 &= 和 ~ 来清理一个标志;
  • | 来合并标志。

这个或许不常用,在 typescript 关于 types 源码中我们也可以看到类似的代码:

字符串类型的枚举可以维护常量:

  1. const enum TODO_STATUS {
  2. TODO = 'TODO',
  3. DONE = 'DONE',
  4. DOING = 'DOING'
  5. }
  6.  
  7. function todos (status: TODO_STATUS): Todo[];
  8.  
  9. todos(TODO_STATUS.TODO)
  10.  

元组

表示一个已知元素数量和类型的数组,各元素的类型不必相同。

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

在发出不固定多个请求时,可以应用:

  1. const requestList: any[] = [http.get<A>('http://some.1')]; // 设置为 any[] 类型
  2. if (flag) {
  3. requestList[1] = (http.get<B>('http://some.2'));
  4. }
  5. const [ { data: a }, response ] = await Promise.all(requestList) as [Response<A>, Response<B>?]

范型

在定义泛型后,有两种方式使用,一种是传入泛型类型,另一种使用类型推断。

  1. declare function fn<T>(arg: T): T; // 定义一个泛型函数
  2. const fn1 = fn<string>('hello'); // 第一种方式,传入泛型类型
  3. string const fn2 = fn(1); // 第二种方式,从参数 arg 传入的类型 number,来推断出泛型 T 的类型是 number

一个扁平数组结构建树形结构例子:

  1. // 转换前数据
  2. const arr = [
  3. { id: 1, parentId: 0, name: 'test1'},
  4. { id: 2, parentId: 1, name: 'test2'},
  5. { id: 3, parentId: 0, name: 'test3'}
  6. ];
  7. // 转化后
  8. [ { id: 1, parentId: 0, name: 'test1',
  9. childrenList: [ { id: 2, parentId: 1, name: 'test2', childrenList: [] } ] },
  10. { id: 3, parentId: 0, name: 'test3', childrenList: [] }
  11. ]
  12.  
  13.  
  14. interface Item {
  15. id: number;
  16. parentId: number;
  17. name: string;
  18. }
  19.  
  20. // 传入的 options 参数中,得到 childrenKey 的类型,然后再传给 TreeItem
  21.  
  22. interface Options<T extends string> {
  23. childrenKey: T;
  24. }
  25. type TreeItem<T extends string> = Item & { [key in T]: TreeItem<T>[] | [] };
  26. declare function listToTree<T extends string = 'children'>(list: Item[], options: Options<T>): TreeItem<T>[];
  27. listToTree(arr, { childrenKey: 'childrenList' }).forEach(i => i.childrenList)
  28.  

infer

表示在 extends 条件语句中待推断的类型变量。

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

这句话的意思是:如果 T 能赋值给 (param: infer P) => any,则结果是 (param: infer P) => any 类型中的参数 P,否则返回为 T。

  1. interface User {
  2. name: string;
  3. age: number;
  4. }
  5. type Func = (user: User) => void
  6. type Param = ParamType<Func>; // Param = User
  7. type AA = ParamType<string>; // string

例子:

  1. // [string, number] -> string | number
  2. type ElementOf<T> = T extends Array<infer E> ? E : never;
  3.  
  4. type TTuple = [string, number];
  5.  
  6. type ToUnion = ElementOf<TTuple>; // string | number
  7.  
  8.  
  9. // T1 | T2 -> T1 & T2
  10. type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
  11.  
  12. type Result = UnionToIntersection<T1 | T2>; // T1 & T2
  13.  

总结

typescript 关于类型限制还是非常强大的,由于文章有限,还有其他类型比如联合类型,交叉类型等读者可自行翻阅资料查看。刚开始接触范型以及其各种组合会感觉不熟练,接下来在项目中会慢慢应用,争取将 bug 降至最低限度。

到此这篇关于typescript实用小技巧的文章就介绍到这了,更多相关typescript实用小技巧内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号