TS Crash | 青训营笔记

102 阅读6分钟

一入TS深似海,这是我参与「第四届青训营」笔记创作活动的第1天

基本类型

数组类型

let list: number[] = [1, 2, 3];
// 二维数组
let vec: number[][] = [[1, 2, 3], [1, 2, 3]];

元组类型

元组是固定长度的数组,每个元素有自己的类型

// 声明一个元组,包含两个元素,第一个元素为string类型,第二个元素为number类型
let x: [string, number];

// 正确
x = ['hello', 10];

// 错误;元素类型不匹配
// error: Type 'number' is not assignable to type 'string'
// error: Type 'string' is not assignable to type 'number'
x = [10, 'hello'];

// 错误;长度不匹配
// error: Property '1' is missing in type '[string]' but required in type '[string, number]'
x = ['hello'];

枚举类型

enum Direction {
  Up,   // 值默认为 0
  Down, // 值默认为 1
  Left, // 值默认为 2
  Right // 值默认为 3
}

console.log(Direction.Up) //0
console.log(Direction[0]) //Up

enum Calc {
  add = '+' ,
  mult = '*',
}
console.log(Calc.add)  // +
console.log(Calc.mult) // *

//error: Property '+' does not exist on type 'typeof Calc'.
console.log(Calc['+']) 

只有数字枚举对象支持反向映射

简单类型

  • 符号类型symbol
const sym = Symbol();
console.log(sym) // "Symbol()"

let obj = {
  [sym]: "value",
  sym: 'I am not a symbol'
};
console.log(obj[sym]); // "value"
console.log(obj.sym); // "I am not a symbol"
  • Null类型 和 Undefined类型
  • Never类型
// 声明不可能存在的交叉类型(取交集)
let n: boolean & number;
// 等价于
let n: never;
  • 任意值类型any

复合类型

  • 联合类型 Union Type |
let foo: number | "foo" | boolean

//ok
foo = 1 
foo = "foo" 
foo = true

//error: Type '"bar"' is not assignable to type 'number | boolean | "foo"'.
foo = "bar" 
  • 交叉类型 Intersection Type &
interface XPoint {
  x: number
  id: string
}

interface YPoint {
  y: number
  id: number
}

type XYPoint = XPoint & YPoint 
//等价于=> {
//   x: number,
//   y: number,
//   id: string & number
//}


let XY1:XYPoint = {
  x: 1,
  y: 1,
  id: 1 as never
} 
  • |&一起使用

interface IHistoryBook {
  author: string
  type: 'history'
  range: string
}

interface IStoryBook {
  author: string
  type: 'story'
  theme: string
}

const bookList: IBookList = [{
  author: 'foo',
  type: 'history',
  range: '2001-2022'
}, {
  author: 'bar',
  type: 'story',
  theme: 'love'
}]

type IBookList = Array<IHistoryBook | IStoryBook>

type IBookList2= Array<
{
  author: string 
} & (
  {
    type: 'history'
    range: string
  } | {
    type: 'story'
    theme: string
  }
)>
// 相当于捆绑了type: 'history'和range,也就是当type为'history'才有range,合理

type IBookList3= Array<
{
  author: string
  type: 'history' | 'story' 
} & (
  {
    range: string
  } | {
    theme: string
  }
)>
// 这种写法当type为'history'或'story'都可以有range,不合理

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

// 通过keyof关键字声明联合
let keyWord: keyof Person;
// 完全等价于
let keyWord: "name" | "age";

keyWord = "name"; //ok
keyWord = "age"; //ok

keyof 关键字生成的是字符串值的联合,每个字符串值都是被获取类型 T 的键的索引名

类类型

类本身是一种类型,可以直接作为类型名

// 定义类
class TypeA {
  // ...
}

// 声明TypeA类型
let a: TypeA;
// 赋值TypeA类型
a = new TypeA();

对象类型(Object Types)

对象类型是匿名的接口类型,对象类型没有名字,接口类型有名字。接口类型相当于为对象类型声明了一个别名。对象是一组键值对的实例

function greet(person: { name: string; age: number }) {
  return "Hello " + person.name;
}

interface

interface Person {
  name: string;
  age: number;
}
  
function greet(person: Person) {
  return "Hello " + person.name;
}

type alias

type Person = {
  name: string;
  age: number;
};
 
function greet(person: Person) {
  return "Hello " + person.name;
}

index signature

索引签名的名称(如:{ [index: string]: { message: string } } 里的 index )除了可读性外,并没有任何意义。例如:如果有一个用户名,可以使用 { username: string}: { message: string },这有利于下一个开发者理解代码。 index只能是number/string/symbol

interface NumberStringArray {
  [index: number]: string
}

//可看成一个字符串数组
let numberStringArray: NumberStringArray = ['foo', 'bar']

let numberStringArray2: NumberStringArray = {
  1: 'foo',
  2: 'bar'
}

interface StringStringArray {
  [index: string]: string
}

let stringStringArray: StringStringArray = {
  foo: 'foo',
  bar: 'bar',
  2: 'dsds'
}

console.log(numberStringArray)//[ 'foo', 'bar' ]
console.log(numberStringArray2)//{ '1': 'foo', '2': 'bar' }
console.log(stringStringArray)//{ '2': 'dsds', foo: 'foo', bar: 'bar' }

泛型(Generic Types)

泛型语法

泛型,即不预先指定具体的类型,而在使用的时候再指定

/*函数泛型,不预先指定具体的类型,而在使用的时候再指定*/
type IGetRepeatArr = <T>(target: T) => T[]


interface IX<T, U> {
	key: T
	val: U
}

泛型约束

在声明泛型时,可以给定一个默认类型

interface MyType<T = string> {
  value: T;
}

// 正确,在类型参数没有显示指定的情况下,采用了默认类型 string
let x1: MyType = {
  value: "hello world"
}
// 等价于
let x1: MyType<string> = {
  value: "hello world"
}

// 错误, error TS2322: Type 'number' is not assignable to type 'string'
let x2: MyType = {
  value: 123
}

类型转换

类型断言

理解为类型转换即可

let someValue: any = "this is a string";

// 1、尖括号语法
let strLength: number = (<string>someValue).length;
// 2、as语法
let strLength: number = (someValue as string).length;

尖括号 <> 的运算符优先级低于点号 . ,因而必须用括号将断言表达式扩起来,直接这样写是错误的:

// 错误,error TS2339: Property 'showColor' does not exist on type 'Shape'
console.log(<Circle>circle.showColor());

工具类型(Utility Types)

Record<Keys,Type>

record跟index signature看起来很像,在一些情况下效果完全一致。

const object1: Record<string, string> = { prop: 'Value' }; // OK
const object2: { [key: string]: string } = { prop: 'Value' }; // OK

但在一些明确特定的keys的情形下,record是最佳选择,而index signature的index只能是number、string这样的基本类型,而不能是具体的某些值或者枚举对象。

interface CatInfo {
  age: number;
  breed: string;
}
 
type CatName = "miffy" | "boris" | "mordred";
 
const cats: Record<CatName, CatInfo> = {
  miffy: { age: 10, breed: "Persian" },
  boris: { age: 5, breed: "Maine Coon" },
  mordred: { age: 16, breed: "British Shorthair" },
};
 
console.log(cats.boris);

Partial< Type >

使Type中所有属性变为可选的

type Partial<T> = {
    [P in keyof T]?: T[P];
};

interface Todo {
  title: string;
  description: string;
}
 
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
  return { ...todo, ...fieldsToUpdate };
}
 
const todo1 = {
  title: "organize desk",
  description: "clear clutter",
};
 
const todo2 = updateTodo(todo1, {
  description: "throw out trash",
});

Required< Type >

使Type中所有属性变为必需的

type Required<T> = {
    [P in keyof T]-?: T[P];
};
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>'.

条件类型(Conditional Types)

Infer

T extends (infer E)[] ? E : T:如果T能够赋值给(infer E)[],则结果是数组的元素E,否则为T

type ArrayElementType<T> = T extends (infer E)[] ? E : T;

// type of item1 is `number`
type item1 = ArrayElementType<number[]>;

// type of item1 is `{name: string}`
type item2 = ArrayElementType<{ name: string }>;

T extends (arg: infer P) => any ? P : T :如果 T 能赋值给 (arg: infer P) => any,则结果是 (arg: infer P) => any 类型中的参数 P,否则返回为 T。

type ParamType<T> = T extends (arg: 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

内置类型ReturnType<T>,只是将infer移到了返回值的部分

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

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

type Func = () => User;
type Return = ReturnType<Func>; // Return = User

特殊符号

  • ?.

可选链操作符 ( ?. ) 类似于 . 链式操作符,不同之处在于,在引用为空 (null 或者 undefined) 的情况下不会引起错误,该表达式短路返回值是 undefined。

下面这行代码的含义是,当 foo 被定义了,foo.bar.baz() 将会执行完成;但是当 foo 是 null 或者 undefined 时,TypeScript 会立即停止运行,并且仅仅是返回 undefined。

let x = foo?.bar.baz();

等价于

let x = foo === null || foo === undefined ? undefined : foo.bar.baz();

可选链操作符常用于从后端接口获取返回对象,但无法保证返回的对象一定存在的情形。避免了需要繁琐使用&&进行多层级的判断

  • ??

空值合并操作符(??)是一个逻辑操作符,当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。

const foo = null ?? 'default string';
console.log(foo);
// expected output: "default string"

const baz = 0 ?? 42;
console.log(baz);
// expected output: 0

参考资料