你可能不知道的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 避免出现新增了联合类型没有对应的实现,目的就是写出类型绝对安全的代码。

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 源码中我们也可以看到类似的代码:

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

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实用小技巧内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 浅谈Vue3.0之前你必须知道的TypeScript实战技巧

    很多人对TypeScript的使用还停留在基本操作上,其实TypeScript的特性非常强大,我们利用好这些特性可以有效地提高代码质量.加速开发效率,今天就介绍9个非常实用的TypeScript技巧或者特性. 注释的妙用 我们可以通过 /** */ 来注释TypeScript的类型,当我们在使用相关类型的时候就会有注释的提示,这个技巧在多人协作开发的时候十分有用,我们绝大部分情况下不用去花时间翻文档或者跳页去看注释. 巧用类型推导 TypeScript 能根据一些简单的规则推断(检查)变量的类型

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

    目录 前言 函数重载 映射类型 Partial, Readonly, Nullable, Required Pick, Record Exclude, Omit ReturnType 类型断言 枚举 元组 范型 infer 总结 前言 用了很久的 typescript,用了但感觉又没完全用.因为很多 typescript 的特性没有被使用,查看之前写的代码满屏的 any,这样就容易导致很多 bug,也没有发挥出 typescript 真正的"类型"威力.本文总结了一些使用 typesc

  • 关于Go你不得不知道的一些实用小技巧

    目录 Go 箴言 Go 之禅 代码 使用 go fmt 格式化 多个 if 语句可以折叠成 switch 用 chan struct{} 来传递信号, chan bool 表达的不够清楚 30 * time.Second 比 time.Duration(30) * time.Second 更好 用 time.Duration 代替 int64 + 变量名 按类型分组 const 声明,按逻辑和/或类型分组 var 不要在你不拥有的结构上使用 encoding/gob 不要依赖于计算顺序,特别是在

  • 你可能不知道的Vim使用小技巧

    一.用拷贝的内容替换 当发生拼写错误或者想要重命名标识符时,就需要用拷贝的内容来替换当前的名字.比如调用函数时写错了: void letus_fuckit_with_vim(){ cout<<"great!"; } let_fuckat_with_vom(); 只需要先复制上面的函数名,再把光标切换到拼错的词首.然后按下viwp,就替换过来了: void letus_fuckit_with_vim(){ cout<<"great!"; } l

  • 关于TypeScript开发的6六个实用小技巧分享

    目录 1. 开发之前确定实体类型 2. 请求接口时只需要定义自己需要用到的字段 3. 使用枚举类型 4. DOM元素的类型要正常给 5.对象的类型要怎么给 6.结构赋值时类型怎么给 总结 本文总结一下使用TypeScript开发应用程序的一点小经验.说之前,推荐一个VSCODE立即执行TS代码的插件quokka.js, 使用方式,ctrl+shipt+p,输入关键字quokka 回车之后,输入代码之后会立即执行 1. 开发之前确定实体类型 在正式编码之前,如果没有接口文档的话,最好能拿到数据字典

  • Kotlin开发的一些实用小技巧总结

    前言 随着Google I/O大会的召开,Google宣布将支持Kotlin作为Android的开发语言,最近关于Kotlin的文章.介绍就异常的活跃. 本文主要给大家介绍了关于Kotlin开发的一些实用小技巧,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 1.Lazy Loading(懒加载) 延迟加载有几个好处.延迟加载能让程序启动时间更快,因为加载被推迟到访问变量时. 这在使用 Kotlin 的 Android 应用程序而不是服务器应用程序中特别有用.对于 Androi

  • JavaScript编程的10个实用小技巧

    在这篇文章中,我将列出10个Javascript实用小技巧,主要面向Javascript新手和中级开发者.希望每个读者都能至少从中学到一个有用的技巧. 1.变量转换 看起来很简单,但据我所看到的,使用构造函数,像Array()或者Number()来进行变量转换是常用的做法.始终使用原始数据类型(有时也称为字面量)来转换变量,这种没有任何额外的影响的做法反而效率更高. 复制代码 代码如下: var myVar   = "3.14159",str     = ""+ m

  • 总结MySQL建表、查询优化的一些实用小技巧

    MySQL建表阶段是非常重要的一个环节,表结构的好坏.优劣直接影响着后续的管理维护,赶在明天上班前分享总结个人MySQL建表.MySQL查询优化积累的一些实用小技巧. 技巧一.数据表冗余记录添加时间与更新时间 我们用到的很多数据表大多情况下都会有表记录的"添加时间(add_time)",我建议大家再新增一个记录"更新时间(update_time)"字段,在我的工作里需要为市场部.运营部等建立各种报表,而很多报表里的数据都是需要到大记录表里去查询的,如果直接查询大表的

  • jQuery实用小技巧_输入框文字获取和失去焦点的简单实例

    jQuery实用小技巧_输入框文字获取和失去焦点的简单实例 <input id="txt" class="text1" type="text" /> <script src="js/jquery-1.7.1.min.js"></script> <script type="text/javascript"> $(function () { $("inp

  • 常用的10个Python实用小技巧

    大家好,都说追女孩方法大于态度,学Python也是,今天就给大家分享的是我在用Python编写程序时常用的一些小技巧. 1.多次打印同一个字符 在Python中,不用特地写一个函数来重复打印同一个字符,直接使用Print就可以 tem = 'I Love Python ' print(tem * 3) I Love Python I Love Python I Love Python 2.在函数内部使用生成器 在写Python程序时,我们可以在函数内部直接使用生成器,这样可以使代码更简洁. su

  • pandas参数设置的实用小技巧

    前言 在日常使用pandas的过程中,由于我们所分析的数据表规模.格式上的差异,使得同样的函数或方法作用在不同数据上的效果存在差异. 而pandas有着自己的一套参数设置系统,可以帮助我们在遇到不同的数据时灵活调节从而达到最好的效果,本文就将介绍pandas中常用的参数设置方面的知识. 图1 1 设置DataFrame最大显示行数 pandas设置参数中的display.max_rows用于控制打印出的数据框的最大显示行数,我们使用pd.set_option()来有针对的设置参数,如下面的例子:

随机推荐