TS Tricks

TS大法好!
[TOC]

TS Tricks

巧用注释

/** A cool guy. */
interface Person {
  /** A cool name. */
  name: string,
}

image

巧用 typeof

我们一般先写类型,再使用:

interface Opt {
  timeout: number
}
const defaultOption: Opt = {
  timeout: 500
}

有时候可以反过来:

const defaultOption = {
  timeout: 500
}
type Opt = typeof defaultOption

当一个 interface 总有一个字面量初始值时,可以考虑这种写法以减少重复代码

巧用查找类型

interface Person {
  addr: {
    city: string,
    street: string,
    num: number,
  }
}

当需要使用 addr 的类型时,除了把类型提出来

interface Address {
  city: string,
  street: string,
  num: number,
}
interface Person {
  addr: Address,
}

还可以

Person["addr"] // This is Address.

有些场合后者会让代码更整洁、易读。

巧用查找范型

interface API {
  '/user': { name: string },
  '/menu': { foods: Food[] },
}
const get = <URL extends keyof API>(url: URL): Promise<API[URL]> => {
  return fetch(url).then(res => res.json())
}

image
image

巧用显式泛型

$(‘button’) 是个 DOM 元素选择器,可是返回值的类型是运行时才能确定的,除了返回 any ,还可以

function $<T extends HTMLElement>(id: string):T {
  return document.getElementById(id)
}

// Tell me what element it is.
$<HTMLInputElement>('input').value

函数泛型不一定非得自动推导出类型,有时候显式指定类型就好。

Lookup Types / 查找类型

[] 的类型版。

T[K]返回 (类型T中以K为属性名的值) 的类型。K 必须是 keyof T 的子集,可以是一个字符串字面量。

const a = { k1: 1, k2: "v2" };

// tv1 为number
type tv1 = (typeof a)["k1"];

// tv2 为string
type tv2 = (typeof a)["k2"];

// tv$ 为 (number|string): 属性名的并集对应到了属性值的类型的并集
type tv$ = (typeof a)["k1" | "k2"];

// 以上的括号不是必需的: typeof 优先级更高

// 也可以用于获取内置类型 (string 或 string[]) 上的方法的类型

// (pos: number) => string
type t_charAt = string["charAt"];  

// (...items: string[]) => number
type t_push = string[]["push"];

Readonly

使用上面介绍的新特性可以定义出一些可用作 类型的 decorator 的泛型,比如下面的 Readonly (已经在TS2.1标准库中):

/**
 * Make all properties in T readonly
 */
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

interface A {
    k1: string;
    k2: string;
    k3: number;
}

/**
 类型运算的结果为
type A_ro = {
    readonly k1: string;
    readonly k2: string;
    readonly k3: number;
}
 */
type A_ro = Readonly<A>;

Readonly的强化版: DeepReadonly

前面提到的 Readonly 只限制属性只读,不会把属性的属性也变成只读:

const v = { k1: 1, k2: { k21: 2 } };

const v_ro = v as Readonly<typeof v>;

// 属性: 不可赋值
v_ro.k1 = 2; 
// 属性的属性: 可以赋值
v_ro.k2.k21 = 3;

我们可以写一个DeepReadonly,实现递归的只读:

type DeepReadonly<T> = {
    readonly [P in keyof T]: DeepReadonly<T[P]>;
};

const v_deep_ro = v as any as DeepReadonly<typeof v>;
// 属性: 不可赋值
v_deep_ro.k1 = 2;
// 属性的属性: 也不可赋值
v_deep_ro.k2.k21 = 3;

DeepReadonly 是怎样展开的

背景: 如果 A 是泛型的类型参数 (比如 T<A>),则称形如 { [P in keyof A]: (类型表达式) } 的映射类型为 A 的 同构 (isomorphic) 类型。这样的类型含有和 A 相同的属性名,即相同的”形状”。在展开 T<A> 时有如下的附加规则:

  1. 基本类型 (string | number | boolean | undefined | null)的同构类型强行定义为其本身,即跳过了对值类型的运算

  2. union 类型 (如 type A = A1 | A2) 的同构类型 T<A> 展开为T<A1> | T<A2>

所以上面的 DeepReadonly<typeof v>的 (概念上) 展开过程是这样的 :

type T_DeepRO = DeepReadonly<{ k1: number; k2: { k21: number } }>

type T_DeepRO = {
    readonly k1: number;
    readonly k2: DeepReadonly<{ k21: number }>;
}

type T_DeepRO = {
    readonly k1: number;
    readonly k2: {
        readonly k21: DeepReadonly<number>;
    }
}

↓ (规则1)

type T_DeepRO = {
    readonly k1: number;
    readonly k2: {
        readonly k21: number;
    }
}

巧用Omit

利用PickExclude组合成Omit。

type Omit<T, K> = Pick<T, Exclude<keyof T, K>>

image

泛型

function identity<T>(arg: T): T {
    return arg;
}
/** We can also write the generic type as a call signature of an object literal type: */
let myIdentity: {<T>(arg: T): T} = identity;

泛型的限制

function loggingIdentity<T>(arg: T): T {
    console.log(arg.length);  // Error: T doesn't have .length
    return arg;
}

可以通过interface和extends关键字解决限制

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // Now we know it has a .length property, so no more error
    return arg;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值