JavaScript 专栏 - TypeScript 基础类型

2021-06-17

额外补充

非空

TypeScript 基础类型

Boolean\Number\String\Array\Typle

Tuple(元组) 类型

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

// Declare a tuple type
let x: [string, number]
// Initialize it
x = ['hello', 10] // OK
// Initialize it incorrectly
x = [10, 'hello'] // Error

当访问一个已知索引的元素,会得到正确的类型:

console.log(x[0].substr(1)) // OK
console.log(x[1].substr(1)) // Error, 'number' does not have 'substr'

当访问一个越界的元素,会使用联合类型替代:

x[3] = 'world' // OK, 字符串可以赋值给(string | number)类型
console.log(x[5].toString()) // OK, 'string' 和 'number' 都有 toString
x[6] = true // Error, 布尔不是(string | number)类型

react useState 返回的就是一个元组类型

Unknown 类型

let value: unknown

value = true // OK
value = 42 // OK
value = 'Hello World' // OK
value = [] // OK
value = {} // OK
value = Math.random // OK
value = null // OK
value = undefined // OK
value = new TypeError() // OK
value = Symbol('type') // OK

可进行随意赋值,不可随便访问

let value: unknown

value.foo.bar // Error
value.trim() // Error
value() // Error
new value() // Error
value[0][1] // Error

访问前作类型判断

let fun: unknown
fun = 123
if (typeof fun === 'function') {
	fun()
}

unknown 类型只能被赋值给 any 类型和 unknown 类型本身

let value: unknown

value.foo.bar // Error
value.trim() // Error
value() // Error
new value() // Error
value[0][1] // Error

Void 类型

void 类型像是与 any 类型相反,它表示没有任何类型

// 声明函数返回值为void
function warnUser(): void {
  console.log("This is my warning message");
}

Null 和 Undefined

TypeScript 里,undefined 和 null 两者各自有自己的类型分别叫做 undefined 和 null

let u: undefined = undefined
let n: null = null

如果关闭了 --strictNullChecks 标记,undefined 和 null 可以赋值给别的类型

let a = 1
a = undefined

我们默认都是开启 strictNullChecks,如果想将 undefined 和 null 赋值给别的类型,使用联合类型

let a: number | null | undefined = 1
a = undefined

Never 类型

never 类型表示的是那些永不存在的值的类型

// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
	throw new Error(message)
}
function infiniteLoop(): never {
	while (true) {}
}

可以利用 never 类型的特性来实现全面性检查

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
	}
}

如果有一天改了 Foo 类型

type Foo = string | number | boolean

而忘记同时修改 controlFlowAnalysisWithNever 方法中的控制流程,这时候 else 分支的 foo 类型会被收窄为 boolean 类型,导致无法赋值给 never 类型,这时就会产生一个编译错误

Enum 类型

enum Direction {
  NORTH,
  SOUTH,
  EAST,
  WEST,
}
let dir: Direction = Direction.NORTH;
console.log(dir) // 0 NORTH初始为0,其余的成员会从 1开始自动增长


enum Direction {
  NORTH = 1,
  SOUTH,
  EAST,
  WEST,
}
let dir: Direction = Direction.NORTH;
console.log(dir) // 1 NORTH初始为1,其余的成员会从2开始自动增长


// 字符串枚举
enum Direction {
  NORTH = "NORTH",
  SOUTH = "SOUTH",
  EAST = "EAST",
  WEST = "WEST",
}
console.log(Direction.NORTH)// NORTH

枚举是在运行时真正存在的对象

反向映射

enum Enum {
    A
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"

以上代码对于的 ES5 代码如下

var Enum
;(function (Enum) {
	Enum[(Enum['A'] = 0)] = 'A'
})(Enum || (Enum = {}))
var a = Enum.A
var nameOfA = Enum[a] // "A"

可以解读为

var Enum = {}
Enum['A'] = 0
Enum[0] = 'A'
var a = Enum.A // 0
var nameOfA = Enum[a] // "A"

TypeScript 断言

let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

联合类型和类型别名

// 联合类型
let a: number | string | undefined

// 类型别名
type A = number | string | undefined
let a: A
type User = {
	name: string,
	age: number,
}

交叉类型

交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所有类型的特性。

type Man = {
	name: string,
	age: number,
	gender: '男' | '女',
}

type Women = {
	name: string,
	age: number,
}

type User = Man & Women

const user: User = {
	name: '1212',
	age: 18,
	gender: '女',
}

TypeScript 函数

函数类型

function add(x: number, y: number): number {
	return x + y
}

const add1 = function (x: number, y: number): number {
	return x + y
}

const add2 = (x: number, y: number): number => {
	return x + y
}

完整函数类型

const myAdd: (baseValue: number, increment: number) => number = (x: number, y: number): number => {
	return x + y
}

或者

type SomeFunType = (baseValue: number, increment: number) => number
// TS 默认推导出 x y 的类型
const myAdd: SomeFunType = (x, y): number => {
	return x + y
}

可选参数及默认参数

// 可选参数
function createUserId(name: string, id: number, age?: number): string {
	return name + id
}

// 默认参数
function createUserId(name: string = 'Semlinker', id: number, age?: number): string {
	return name + id
}

在声明函数时,可以通过 ? 号来定义可选参数,比如 age?: number 这种形式。在实际使用时,需要注意的是可选参数要放在普通参数的后面,不然会导致编译错误。

使用了 --strictNullChecks,可选参数会被自动地加上 | undefined:

剩余参数

function buildName(firstName: string, ...restOfName: string[]) {
	return `${firstName} ${restOfName.join(' ')}`
}
const employeeName = buildName('Joseph', 'Samuel', 'Lucas', 'MacKinzie')

TypeScript 数组

数组解构

const five_array = [0, 1, 2, 3, 4]
const [x, y, z] = five_array

数组展开运算符

let two_array = [0, 1]
let five_array = [...two_array, 2, 3, 4]

TypeScript 对象

对象解构

let person = {
	name: 'Semlinker',
	gender: 'Male',
}
let { name, gender } = person

对象展开运算符

const person = {
	name: 'Semlinker',
	gender: 'Male',
	address: 'Xiamen',
}

// 组装对象
const personWithAge = { ...person, age: 33 }

// 获取除了某些项外的其它项
const { name, ...rest } = person

const { gender, address } = rest

TypeScript 接口

在面向对象语言中,接口是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类去实现。 TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。

对象的形状

interface Person {
	name: string;
	age: number;
}
let Semlinker: Person = {
	name: 'Semlinker',
	age: 33,
}

可选 | 只读属性

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

函数类型

interface SearchFunc {
	(source: string, subString: string): boolean;
}

可索引的类型

与使用接口描述函数类型差不多,我们也可以描述那些能够“通过索引得到”的类型,比如 a[10]或 ageMap["daniel"]。 可索引类型具有一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。

interface StringArray {
	[index: number]: string;
}

let myArray: StringArray
myArray = ['Bob', 'Fred']

let myStr: string = myArray[0]

接口 vs 类型别名

type Alias = { num: number }
interface Interface {
	num: number;
}
declare function aliased(arg: Alias): Alias
declare function interfaced(arg: Interface): Interface

在编译器中将鼠标悬停在 interfaced 上,显示它返回的是 Interface,但悬停在 aliased 上时,显示的却是对象字面量类型。

类型别名不能被 extends 和 implements(自己也不能 extends 和 implements 其它类型)

接口声明时不能使用联合类型或交叉类型

类型守卫

联合类型中我们只能访问共同拥有的成员。

interface Bird {
    fly();
    layEggs();
}

interface Fish {
    swim();
    layEggs();
}

function getSmallPet(): Fish | Bird {
    // ...
}

let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim();    // errors


let pet = getSmallPet();

// JavaScript里常用来区分2个可能值的方法是检查成员是否存在,这里每一个成员访问都会报错
if (pet.swim) {
    pet.swim();
}
else if (pet.fly) {
    pet.fly();
}


let pet = getSmallPet();
// 使用断言
if ((<Fish>pet).swim) {
    (<Fish>pet).swim();
}
else {
    (<Bird>pet).fly();
}

自定义类型保护的类型谓词

function isFish(pet: Fish | Bird): pet is Fish {
    return (<Fish>pet).swim !== undefined;
}

pet is Fish 就是类型谓词。 谓词为 parameterName is Type 这种形式, parameterName 必须是来自于当前函数签名里的一个参数名。

// 'swim' 和 'fly' 调用都没有问题了

if (isFish(pet)) {
	pet.swim()
} else {
	pet.fly()
}

TypeScript 不仅知道在 if 分支里 pet 是 Fish 类型; 它还清楚在 else 分支里,一定 不是 Fish 类型,一定是 Bird 类型。

in 关键字

interface Admin {
	name: string;
	privileges: string[];
}

interface Employee {
	name: string;
	startDate: Date;
}

type UnknownEmployee = Employee | Admin

function printEmployeeInformation(emp: UnknownEmployee) {
	console.log('Name: ' + emp.name)
	if ('privileges' in emp) {
		console.log('Privileges: ' + emp.privileges)
	}
	if ('startDate' in emp) {
		console.log('Start Date: ' + emp.startDate)
	}
}

typeof 关键字

function padLeft(value: string, padding: string | number) {
	if (typeof padding === 'number') {
		return Array(padding + 1).join(' ') + value
	}
	if (typeof padding === 'string') {
		return padding + value
	}
	throw new Error(`Expected string or number, got '${padding}'.`)
}

typeof 类型保护只支持两种形式:typeof v === "typename" 和 typeof v !== typename,"typename" 必须是 "number", "string", "boolean" 或 "symbol"。 但是 TypeScript 并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。

instanceof 关键字

interface Padder {
  getPaddingString(): string;
}

class SpaceRepeatingPadder implements Padder {
  constructor(private numSpaces: number) {}
  getPaddingString() {
    return Array(this.numSpaces + 1).join(" ");
  }
}

class StringPadder implements Padder {
  constructor(private value: string) {}
  getPaddingString() {
    return this.value;
  }
}

let padder: Padder = new SpaceRepeatingPadder(6);

if (padder instanceof SpaceRepeatingPadder) {
  // padder的类型收窄为 'SpaceRepeatingPadder'
}

TypeScript 泛型

当我们需要使用一个函数, 当它传入数字的时候, 需要返回这个数字

function id(value: number): number {
	return value
}

但是这个函数的需求是, 需要在传入 string 的时候, 也能够返回 string, 一般情况下, 我们可以这样写

function id(value: number | string): number | string {
	return value
}

这种方法也可以, 但是无法完全限定死它的类型, 因为我们的需求是传入的类型等于传出的类型, 在这个类型定义中, 我们可以传入 number, 传出 string, 也是能够通过类型检测的 如果传入的 value 有很多种类型怎么办呢? 这个时候就需要使用到泛型了

function id<T>(value: T): T {
	return value
}
// 常规调用
id < number > 1
// 简略调用(自动类型判断)
id(1)

使用典型场景: 当我们需要一个函数, 函数传入的值与传出值存在某种特定数据类型联系的时候, 需要使用到泛型

除了为类型变量显式设定值之外,一种更常见的做法是使编译器自动选择这些类型,从而使代码更简洁。我们可以完全省略尖括号,比如:

function identity<T, U>(value: T, message: U): T {
	console.log(message)
	return value
}

console.log(identity(68, 'Semlinker'))

上面的例子,如果想要返回两种类型,我们可以这样

// 使用元组
function identity<T, U>(value: T, message: U): [T, U] {
	return [value, message]
}

除此之外,还可以使用泛型接口

泛型接口

interface Identities<V, M> {
	value: V;
	message: M;
}

function identity<T, U>(value: T, message: U): Identities<T, U> {
	console.log(value + ': ' + typeof value)
	console.log(message + ': ' + typeof message)
	let identities: Identities<T, U> = {
		value,
		message,
	}
	return identities
}

console.log(identity(68, 'Semlinker'))

泛型约束

function identity<T>(arg: T): T {
	console.log(arg.length) // Error
	return arg
}

在这种情况下,编译器将不会知道 T 确实含有 length 属性,尤其是在可以将任何类型赋给类型变量 T 的情况下。我们需要做的就是让类型变量 extends 一个含有我们所需属性的接口,比如这样:

interface Length {
  length: number;
}

function identity<T extends Length>(arg: T): T {
  console.log(arg.length); // 可以获取length属性
  return arg;
}

T extends Length 用于告诉编译器,我们已经实现 Length 接口的任何类型。之后,当我们使用不含有 length 属性的对象作为参数调用 identity 函数时,TypeScript 会提示相关的错误信息:

identity(68) // Error
// Argument of type '68' is not assignable to parameter of type 'Length'.(2345)

泛型参数默认类型

interface A<T=string> {
  name: T;
}
const strA: A = { name: "Semlinker" };
const numB: A = { name: 101 };// error,不能将类型“number”分配给类型“string”。ts(2322)
const numC<number>: A = { name: 101 };

泛型条件类型

条件类型会以一个条件表达式进行类型关系检测,从而在两种类型中选择其一:


T extends U ? X : Y

若 T 能够赋值给 U,那么类型是 X,否则为 Y


  type K = 1 | 2 | 3 | 4;
  type A<T> = {
    key1: T;
    key2: T extends K ? number : string;
  };

  const a1: A<5> = {
    key1: 5,
    key2: '123',
  };

  const a2: A<4> = {
    key1: 4,
    key2: 123,
  };

特殊关键字

keyof

获取接口或类型的键名,返回的是联合类型

interface IUser {
name: string;
age: number;
}
type TUserKey = keyof IUser; // type TUserKey = "name" | "age"
// 一般来讲, 不建议对一些普通的 string 类型等使用 keyof, 因为 js 本身的对象 key, 可能会产生非预期的结果
type T2 = keyof keyof TUserKey; // type T2 = "toString" | "valueOf"

typeof

获取常量的类型

const a = { a: 1, b: 2, c: '3' }

type T0 = typeof a // type T0 = {a: number; b: number; c: string}

in

遍历联合类型

const a = { a: 1, b: 2, c: '3' };

type T2 = keyof typeof a; // type T2 = "a" | "b" | "c"

type T3 = { [p in T2]: number }; // type T3 = { a: number; b: number; c: number; }

infer

表示带推断的一个类型变量

假设我们有一个这样的需求, 需要获取到函数参数的类型, 并把这个类型赋值给另一个类型, 这个时候我们可以这样写

type TParamType<T> = T extends (param: infer P) => any ? P : T;

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

type TFunc = (user: IUser) => void;
type TParam = TParamType<TFunc>; // type TParam = User;
type TStr = TParamType<string>; // TStr = string;

内置的类型别名

Partial

改所有键值为可选

// ts 中的实现方式
type Partial<T> = {
[P in keyof T]?: T[P];
};

// 使用方式
interface IUser {
name: string;
age?: number;
}
type TPartialUser = Partial<IUser>;
// 输出的 TPartialUser 结果
// type TPartialUser = {
// name?: string | undefined;
// age?: number | undefined;
// }

Required

改所有键值为必选

// ts 中的实现方式
type Required<T> = {
[P in keyof T]-?: T[P];
};
// 使用方式
interface IUser {
name: string;
age?: number;
}
// 改 IUser 的接口值为均必选
type TRequiredUser = Required<IUser>;
// 输出的 TRequiredUser 结果
// type TRequiredUser = {
// name: string;
// age: number;
// }

Readonly

将传入属性变为只读

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

给子属性添加 readonly 的标识,如果将上面的 readonly 改成 -readonly, 就是移除子属性的 readonly 标识。

Pick

将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型。

// node_modules/typescript/lib/lib.es5.d.ts

type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type TodoPreview = Pick<Todo, 'title' | 'completed'>;

const todo: TodoPreview = {
  title: 'Clean room',
  completed: false,
};

Record

Record<K extends keyof any, T> 的作用是将 K 中所有的属性的值转化为 T 类型。

 type Record<K extends keyof any, T> = {
  [P in K]: T;
};

interface PageInfo {
  title: string;
}

type Page = 'home' | 'about' | 'contact';

const x: Record<Page, PageInfo> = {
  about: { title: 'about' },
  contact: { title: 'contact' },
  home: { title: 'home' },
};

Exclude

将某个类型中属于另一个的类型移除掉

type Exclude<T, U> = T extends U ? never : T;

type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number

Extract

与 Exclude 恰好相反, 代表取出对应值

type Extract<T, U> = T extends U ? T : never;

type T0 = Extract<'a' | 'b' | 'c', 'a'>; // "a"

ReturnType

ReturnType 的作用是用于获取函数 T 的返回类型。

type ReturnType<T extends (...args: any) => any> = T extends (
  ...args: any
) => infer R
  ? R
  : any;	

ThisType

指定上下文的类型

ThisType 在 TS 的类型声明层面其实就是一个空的对象类型, 但是通过源码层面对这个内置类型别名进行了支持

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

const obj: ThisType<Person> = {
	dosth() {
	this.name // string
	}
}

NonNullable

过滤类型中的 null 和 undefined

type NonNullable<T> = T extends null | undefined ? never : T;

Parameters

获取函数的传入值的所有参数

type Parameters<T extends (...args: any[]) => any> = T extends (...args: infer P) => any ? P : never;

常用的自定义类型别名

Omit

移除掉类型中的某个值

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

Deferred

返回内容值为 Promise

type Deferred<T> = {
   [P in keyof T]: Promise<T[P]>;
};

AsyncReturnType

解开 Promise

type AsyncReturnType<T> = T extends Promise<infer U>
? U
: T extends (...args: any) => Promise<infer U>
? U
: T extends (...args: any) => infer U
? U
: T;

const fetchDepRolesWithTag = (deptCode: string, servicelineCode: string) => {
  return (
    request <
    IRequestResult<{
      records: {
        roleCode: string;
        roleName: string;
        tag: {
          code: string;
          name: string;
        }[];
      }[];
    }>(`/api/bms/department/role/tag`, {
      method: 'get',
      params: {
        deptCode,
        servicelineCode,
        current: 1,
        size: -1,
      },
    })
  );
};

type FetchDepRolesWithTagType = typeof fetchDepRolesWithTag;
// type FetchDepRolesWithTagType = (deptCode: string, servicelineCode: string) => Promise<IRequestResult<{
// records: {
// roleCode: string;
// roleName: string;
// tag: {
// code: string;
// name: string;
// }[];
// }[];
// }>>

type FetchDepRolesWithTagReturnType = AsyncReturnType<FetchDepRolesWithTagType>;
// type FetchDepRolesWithTagReturnType = IRequestResult<{
// records: {
// roleCode: string;
// roleName: string;
// tag: {
// code: string;
// name: string;
// }[];
// }[];
// }>
type GenericType<T> = T extends IRequestResult<infer U> ? U : T;

type FetchDepRolesWithTagReturnResultType = GenericType<FetchDepRolesWithTagReturnType> ;
// type FetchDepRolesWithTagReturnResultType = {
//   records: {
//     roleCode: string;
//     roleName: string;
//     tag: {
//       code: string;
//       name: string;
//     }[];
//   }[];
// };

// 上面转换合并为
type RequestResultType<T> = GenericType<AsyncReturnType<T>>;
type ResultType = RequestResultType<typeof fetchDepRolesWithTag>;

PartialOptional

type PartialOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

PartialOmit

type PartialOmit<T, K extends keyof T> = Pick<T, K> & Partial<Omit<T, K>>;

补充

显式赋值断言

let x!: number[];
initialize();
x.push(4);

function initialize() {
x = [0, 1, 2, 3];
}

非空断言

ttype A = {
  key1?: {
    key1_1: number;
  };
};
const a: A = {
  key1: {
    key1_1: 1,
  },
};
console.log(a.key1!.key1_1);

可选链

type A = {
  key1?: {
    key1_1: number;
  };
};
const a: A = {
  key1: {
    key1_1: 1,
  },
};
console.log(a.key1?.key1_1);
// console.log((_a = a.key1) === null || _a === void 0 ? void 0 : _a.key1_1);

空值合并运算

let x = foo ?? bar();
// let x = foo !== null && foo !== void 0 ? foo : bar();

简单的一个ts编译器

https://github.com/sandersn/mini-typescript

copyright ©2019-2024 shenzhen
粤ICP备20041170号-1