TS 高阶技巧

参考:

15 个提高开发效率的 TypeScript 小技巧

天花板级别类型体操

⚪ ? 与 !

  • ?为可选链式编程,js、ts 皆可使用,遇到 null 或 undefined 就不会再向下执行
  • !叫非空断言操作符,为 Ts 专属语法,断言某变量一定不为 null 或 undefined
    • 不使用 !
    • 使用 !

⚪ 断言覆盖

**as unknown as otherType**** 可覆盖重写原类型。 比 any 优雅太多**

1
2
3
let number = 12;
let str: string;
str = number as unknown as string;

⚪ keyof 与 typeof

  • typeof 可以用于类型判断,function 定义的函数会返回函数类型,const 常量定义的变量也可转为其对应的类型
  • keyof 则可返回 类型上已知的公共属性名

二者可以结合使用

  • 例:
1
2
3
4
const obj = {
name: "目力先辈",
age: 24,
};

如果想把 obj 转为'name' | 'age' 这种类型,以在某些业务场景中达到更好的代提示和类型限制,可以这样:

先用 typeof 把 obj 转为 类型,然后将通过 keyof 获取类型上已知的属性名

1
type objType = keyof typeof obj;

⚪ 将对象键值的类型转为联合类型

1
2
3
4
5
6
7
8
9
interface Obj {
name: string;
sex: number;
age: number;
body: {
hand: string;
};
}
type ObjType = Obj[keyof Obj];

⚪ const 和 let 类型推导的区别

const 声明的常量的类型为**<font style="color:rgb(51, 51, 51);">字面量类型</font>**

1
2
3
4
5
6
7
8
9
10
const a = 1; // 则 a 的类型就为 1
const d = "2"; // 则 a 的类型就是 '2'
// 这是因为常量是不可修改的,所以不会进行类型推断,但下面的情况不是这样
// b 的类型为 number,c 的类型为 string,这是由于右侧得到的类型即为 (number | string)[]
const [b, c] = [1, "2"];
// d.e 的 类型 为 number,d.f 的类型 为 string,因为常量对象的属性是可以进行操作的,类型推导也会发生在初始化成员(对象属性)的过程中
const d = {
e: 1,
f: "2",
};

let 声明的变量时,类型会被推导

1
2
let a = 1; // a 为 number 类型
let b = "2"; // b 为 string 类型

**as const**** 断言:**将推导的类型强转为字面量。可理解为 let 转 const

1
2
3
4
5
6
7
8
9
const arg = { b: 1 };
const fn = (params: { b: 1 }) => {};

// 报错:不能将类型 “number” 分配给类型“1”
fn(arg);

// 因为arg.b 为 number 类型。 params.b 类型为 “1” 的字面量。
// arg as const即可
const arg = { b: 1 } as const;

通过 const 将对象的值作为联合类型

⚪ 函数返回字面量而非 String

1
const ReturnConstStringFn = <Str extends string[]>(val: [...Str]) => val;

⚪ 获取 interface 中的类型

1
2
3
4
5
6
7
8
interface IType {
name: string;
body: Array<{
hand: string;
}>;
}

type Str = IType["body"][number]["hand"];

⚪ 模式匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
type arr = [1, 2, 3];

type num = "123";

type FirstArr<Arr> = Arr extends [infer First, ...infer Rest] ? First : Arr;

type FirstNum<Num> = Num extends `${infer First}2${infer Rest}`
? `${First}_${Rest}`
: Num;

const arrParm: FirstArr<arr> = 1;

const numParm: FirstNum<num> = "1_3";

⚪ 互斥类型

1
2
3
4
// 定义排除类型:将U从T中剔除, keyof 会取出T与U的所有键, 限定P的取值范围为T中的所有键, 并将其类型设为never
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
// 定义互斥类型,T或U只有一个能出现(互相剔除时,被剔除方必须存在)
type XOR<T, U> = (Without<T, U> & U) | (Without<U, T> & T);

⚪ 函数类型中的参数关系限定

例如:如果 a 为 1 的时候,b 必须 传 ‘1’,a 为 2,b 必须传 2

  1. 可以利用函数重载 (函数可以重复声明,但是只能有一个函数拥有函数体去具体实现)
1
2
3
4
5
function fn(a: 1, b: "1"): void;
function fn(a: 2, b: "2"): void;
function fn(a: 1 | 2, b: "1" | "2"): void {
// do something
}
  1. 可以利用泛型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface BType {
1: "1";
2: "2";
}

// 推荐
function fn<AType extends keyof BType>(a: Atype, b: BType[AType]): void {
// do something
}

// 或
function fn<AType extends keyof BType>(
a: Atype,
b: AType extends "1" ? BType["1"] : BType["2"]
): void {
// do something
}
  1. 如果函数只有一个参数。可以直接用联合类型声明参数可能的情况。
1
2
3
function fn({ a, b }: { a: 1; b: "1" } | { a: 2; b: "2" }): void {
// do something
}

⚪ 下划线 转 小驼峰。

** <T> extend any ? [type] : never** 可触发 ts 深层计算(即鼠标悬停类型上,可以看到计算后的结果)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Camel<Obj> = Obj extends any
? {
[key in keyof Obj as CamelCase<
Lowercase<key & string>
>]: Obj[key] extends Array<infer Ele>
? Array<CamelCase<Ele & string>>
: Obj[key] extends Record<string, any>
? Camel<Obj[key]>
: Obj[key];
}
: never;

type CamelCase<Str extends string> = Str extends `${infer Left}_${infer Rest}`
? `${Left}${CamelCase<Capitalize<Rest>>}`
: Str;

原数据:

1
2
3
4
5
6
7
8
9
10
type source = {
Name: string;
Naj_age: number;
sex: {
One_man: number;
Two_woman: string;
Other: symbol;
};
hobby: Array<"paly_good_game" | "one_day_sleep">;
};

类型转换后:


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!