TypeScript 类型体操-语法入门篇

2,039 阅读3分钟

TypeScript 类型体操-语法入门篇

TypeScriptJavaScript 添加了一套静态类型系统,通过 TS Compiler 可以将 TS 编译为 JS,在编译的过程做类型检查。

这样就使得 JavaScript 从动态类型语言变成了静态类型语言,可以在编译期间做类型检查,提前发现一些类型安全问题。

为什么有类型体操

  • 简单的类型系统

比如一个 add 函数既可以做整数加法、又可以做浮点数加法,却需要声明两个函数:

int add(int a, int b) {
    return a + b;
}

double add(double a, double b) {
    return a + b;
}

这样做会感觉有些死板,如果类型能作为参数传递是不是会更好。

  • 支持泛型的类型系统

泛型表示一种通用的类型,它可以代表任何一种类型,也叫做类型参数

add<T>(T a, T b) {
    return a + b;
}

这样的泛型虽然会灵活一点,但是对于 JavaScript 来说可能还有点不太够。

比如我们有一个自定义的对象类型。

强类型中,比如Java,对象都是由一个类new出来的,有了类型,就可以获取到类的信息。

但是 JavaScript 不一样,JavaScript中有对象字面量,可以凭空创建一个对象。

如果有一个函数是一个返回对象某个属性值的函数,类型该怎么写呢?

function getPropValue<T>(obj: T, key): key对应的属性值类型 {
    return obj[key];
}

好像拿到了 T,也不能拿到它的属性和属性值,如果能对类型参数 T 做一些逻辑处理就好了。

  • 支持类型编程的类型系统

Java 里面,拿到了对象的类型就能找到它的类,进一步拿到各种信息,所以类型系统支持泛型就足够了。

但是在 JavaScript 里面,对象可以字面量的方式创建,还可以灵活的增删属性,拿到对象并不能确定什么,所以要支持对传入的类型参数做进一步的处理。

对传入的类型参数(泛型)做各种逻辑运算,产生新的类型,这就是类型编程。

比如上面那个 getProps 的函数,类型可以这样写:

function getPropValue<T extends object, Key extends keyof T>(obj: T, key: Key): T[Key] {
  return obj[key];
}

这里的 keyof TT[Key] 就是对类型参数 T 的类型运算。

TypeScript 的类型系统就是第三种,支持对类型参数做各种逻辑处理,可以写很复杂的类型逻辑。

TypeScript 类型系统支持哪些类型和类型运算?

TypeScript 支持的类型

JavaScript 中支持的类型,TypeScript 也都支持。

  • 简单类型: number、boolean、string、object、bigint、symbol、undefined、null
  • 包装类型: Number、Boolean、String、Object、Symbol。
  • 复杂类型:Class、Array 等。
TypeScript 特有的类型
Tuple 元祖

元组(Tuple)就是元素个数和类型固定的数组类型:

type Tuple = [number, string];
Interface 接口

接口(Interface)可以描述函数、对象、构造器等复合类型

interface IPerson {
  name: string;
  age: number;
}

class Person implements IPerson {
  name: string;
  age: number;
}

const obj: IPerson = {
  name: 'guang',
  age: 18,
};
Enum 枚举

枚举(Enum)是一系列值的复合:

enum Transpiler {
  Babel = 'babel',
  Postcss = 'postcss',
  Terser = 'terser',
  Prettier = 'prettier',
  TypeScriptCompiler = 'tsc',
}

const transpiler = Transpiler.TypeScriptCompiler;
字面量类型

TypeScript 还支持字面量类型,也就是类似 123'aaaa'{ a: 1} 这种值也可以做为类型。

其中,字符串的字面量类型有两种,

  • 普通的字符串字面量,比如 'aaa'
  • 模版字面量,比如  `aaa${string}` ,它的意思是以 aaa 开头,后面是任意 string 的字符串字面量类型。

所以想要约束以某个字符串开头的字符串字面量类型时可以这样写:

image.png

特殊类型
  • never: 代表不可达,比如函数抛异常的时候,返回值就是 never。
  • void: 代表空,可以是 undefined 或 never。
  • any: 是任意类型,任何类型都可以赋值给它,它也可以赋值给任何类型(除了 never)。
  • unknown: 是未知类型,任何类型都可以赋值给它,但是它不可以赋值给别的类型。
类型装饰

TypeScript 的类型系统还支持描述类型的属性,比如是否可选,是否只读等:

interface IPerson {
  readonly name: string;
  age?: number;
}

type tuple = [string, number?];

TypeScript 支持的类型运算

获取类型:typeof

typeof 操作符可以用来获取一个变量声明或对象的类型。

interface Person {
  name: string;
  age: number;
}
const sem: Person = { name: 'semlinker', age: 33 };
type Sem = typeof sem; // -> Person

function toArray(x: number): Array<number> {
  return [x];
}

type Func = typeof toArray; // -> (x: number) => number[]
获取类型的键:keyof

keyof 可以用于获取某种类型的所有键,其返回类型是联合类型。

interface Person {
  name: string;
  age: number;
}

type K1 = keyof Person; // "name" | "age"
type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join"
type K3 = keyof { [x: string]: Person }; // string | number
遍历:in

in 用来遍历联合类型:

type Keys = 'a' | 'b' | 'c';

type Obj = {
  [p in Keys]: any;
};
// -> { a: any, b: any, c: any }
泛型约束:extends

有时候我们定义的泛型不想过于灵活或者说想继承某些类等,可以通过 extends 关键字添加泛型约束。

interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

这时我们需要传入符合约束类型的值,必须包含必须的属性:

loggingIdentity(3); // Error, number doesn't have a .length property

loggingIdentity({ length: 10, value: 3 }); // 正确用法
条件运算:extends ? :

TypeScript 里的条件判断是  extends ? :,可以理解成 if else

type isString<T> = T extends string ? true : false;

type res1 = isString<'123'>;

type res2 = isString<123>;

image-1.png

推导:infer

如何提取类型的一部分呢?答案是 infer。

比如提取元组类型的第一个元素:

type First<Tuple extends unknown[]> = Tuple extends [infer T, ...infer R] ? T : never;

type res = First<[1, 2, 3]>;

image-2.png

联合:|

联合类型(Union) 代表类型可以是几个类型之一

type Union = 1 | 2 | 3;
交叉:&

交叉类型(Intersection)

  • 相同的类型做合并
type ObjType = { a: number } & { c: boolean };

let obj: ObjType = {
  a: 2,
  c: false,
};
  • 不同的类型做舍弃
type ObjType = (number | string | symbol) & string;
// 此时 ObjType 只能是 string
case
  • 修改对象类型的值类型
interface Person {
  name: string;
  age: number;
}

type MyPerson = {
  [P in keyof Person]: { value: Person[P] };
};

image-4.png

  • 修改对象类型的 key 类型
interface Person {
  name: string;
  age: number;
}

type MyPerson = {
  [P in keyof Person as `get${P & string}`]: Person[P];
};

image-5.png