TS

171 阅读8分钟

一、 TS 对应的类型

  1. 原始类型
    • number
    • string
    • boolean
    • null
    • undefined
    • void
    • symbol
    • bigint

TS通过冒号语法来将其类型侧定义的类型附着在JS上

const test: string = '123';

一旦给变量加上了类型定义, 员JS变量的类型就被静态化了, 在初始化时, 就不可赋值其他类型的变量给test变量

  1. 非原始类型
    • array
    • tuple
    • enum
const arr = ['1', 2, '3'];

上面这个数组中的变量, 我们可以看到有两个字符串类型和一个数字类型, 但是TS总的数组类型要求数组中的元素都是同一个类型, 不允许动态变化, 我们可以对arr声明类型——string[]类型

const arr: string[] = ['1', '2', '3'];

// or

const arr: Array<string> = ['1', '2', '3'];

但实际的开发中, arr中的元素不是同一个类型的情况可太多了, 所以在TS中这种情况也是存在的, 就是tuple(元组)类型

tuple

元是一种特殊的数组类型, 它主要用于这样的场景: "一个数组的项数已知, 其中每项的类型也已知" 同样使用这个例子

const arr = ['1', 2, '3'];

现在我们给这个arr附着一个类型, 使得其静态化

const arr: [string, number, string] = ['1', 2, '3'];
interface

为了给单个对象元素进行类型注解, 我们使用interface; 它相当于类型中的JS对象, 用于对函数、类等进行结构类型检查, 所谓的结构类型检查

可选属性

一个对象里面的某些参数我们可能没有, 比如一个待办事项Todo, 有时候没有设置time时间属性, 那么修饰这样的一个对象我们可以改如何? TS提供了可选属性这样一个方便的属性, 具体处理如下:

interface Todo {
  content: string;
  user: string;
  time?: string;
  isCompleted: boolean;
}

可以看到, 只需要在属性类型修饰冒号左边加一个问号就可以了, 此时就相当于告诉TS编译器这个time属性是可选的一个类型

只读属性

只需要在对应的属性前面加上readonly关键字即可

interface Todo {
  content: string;
  readonly user: string;
  time?: string;
  isCompleted: boolean;
}

一旦被定义为只读, 我们修改对应的属性值, 就会报错

const todo: Todo = {
  content: '内容创作',
  user: 'z',
  isCompleted: false,
}
todo.user = 'm'; // error
多余属性检查

在JS中经常会遇到一个对象, 一开始不能确认它是有哪个属性, 但是它的属性却可以动态增加; TS提供一个多余属性检查的写法

interface Todo {
  isCompleted: boolean;
  [propName: string]: any;
}

这里我们将其注解为一定拥有isCompleted属性, 其他的属性可以动态添加, 因为动态添加的属性的值类型我们不清楚, 所以我们用any来表示值类型, 它可以是任意类型

const todo: Todo = {
  content: '内容',
  isCompleted: false,
}

todo.user = 'pftom';
todo.time = '2020-04-04';
Enum

枚举, 主要用于帮助定义一系列命名常量, 常用于给一类变量做类型注解, 它们的值是一组值里面的某一个;

可以看下面一个例子, 将五个用户放到枚举里面:

enum UserId {
  user1,
  user2,
  user3,
  user4,
  user5,
}

接下来, 我们可以给interface里面的Todo接口中的user字段一个更精确的类型注解:

interface Todo {
  content: string;
  user: UserId;
  time: string;
  isCompleted: boolean;
}

这样一来, todo里面的user字段就是上面的五人之一;

数字枚举

上面我们的UserId中的几个枚举值其实都对应着相应的数字, 比如UserId.user1它的值是数字0, UserId.user2数字值是1, 以此类推, 后面几个枚举值分别是数字2, 3, 4;

我们可以手动给其中的某个枚举值赋值一个数字, 这样这个枚举值后面的值会依次在这个赋值的数字上递增, 下面的这个例子的枚举值对应的数字依次是: 0, 6, 7, 8, 9

enum UserId {
  user1,
  user2 = 6,
  user3,
  user4,
  user5,
}
字符串枚举
enum UserId {
  user1 = '123123123',
  user2 = '154645653',
  user3 = '436432424',
  user4 = '346543223',
  user5 = '467645321',
}
异构枚举

在一个枚举里面既可以有字符串值也可以有数字

enum UserId {
  user1 = '123123123',
  user2 = 4,
  user3 = '436432424',
  user4 = 2,
  user5 = '467645321',
}
注解函数
function add(x, y) {
  return x + y;
}

函数的主要部分就是输入和输出, 所以在注解函数的时候, 只需要注解函数的参数和返回值就可以了, 因为上述的函数体内是执行x+y操作, 以xy应该都是number数字类型, 返回值也是number数字类型, 所以我们对上面的函数进行如下的类型注解:

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

有时候返回值也可以不写, TS可以根据参数类型和函数体计算返回值类型, 也就是俗称的自动推断类型机制;

推断类型
const add = function(x, y) {
  return x + y;
}

对于这个例子, 我们可以这样注解:

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

对于上面的注解, TS会进行类型的自动推导, 根据函数类型的结构对比后面的函数, 会自动推断出后面函数的x, y和返回值都为number

可选参数

如果一个函数可能存在一些参数, 但是我们并不是每次都需要传递这些参数, 那么它们就属于可选参数的范围, 如下面的lastName就是可选参数

function buildName(firstName: string, lastName?: string) {}

// e.g
buildName('Tom', 'li');
buildName('Lucy');
重载

重载主要为函数多返回类型服务, 具体来说就是一个函数可能会在内部执行一个条件语句, 根据不同的条件返回不同的值, 有些值可能是不同类型, 这时候我们就可以使用重载来给返回值注解类型, 通过定义一系列同样函数名, 不同参数列表和返回值的函数来注解多类型返回值函数

let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x): any {
  if(typeof x == 'object') {
    let pickedCard = Math.floor(Math.random() * x.length);
    return pickedCard;
  } else if(typeof x == 'number') {
    let pickedSuit = Math.floor(x / 13);
    return { suit: suits[pickedSuit], card: x % 13 };
  }
}

let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

pickCard参数类型可能有多种选项, 对应不同选项的参数类型, 会有不同的返回值类型, 并且我们对参数类型还未知; 针对这种情况, 可以使用重载来表达出来

let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x: { suit: string; card: number }[]): number;
function pickCard(x: number): { suit: string; card: number };
function pickCard(x): any {
  // 如果 x 是 `object` 类型,那么我们返回 pickCard 从 myDeck 里面取出 pickCard1 数据
  if (typeof x == "object") {
    let pickedCard = Math.floor(Math.random() * x.length);
    return pickedCard;
  }
  // 如果 x 是 `number` 类型,那么直接返回一个可以取数据的 pickCard2
  else if (typeof x == "number") {
    let pickedSuit = Math.floor(x / 13);
    return { suit: suits[pickedSuit], card: x % 13 };
  }
}

let myDeck = [
  { suit: "diamonds", card: 2 },
  { suit: "spades", card: 10 },
  { suit: "hearts", card: 4 }
];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

因此, 重载实际上就是函数名一样, 参数列表和返回值不一样

交叉类型

交叉类型就是多个类型, 通过&类型运算符, 合并成一个类型, 这个类型包含了多了类型中的所有类型成员

以请求一组数据为例, 我们需要根据查询的结果打印对应的信息:

  • 请求成功, 返回标志请求成功的状态, 以及目标数据;
  • 请求失败, 返回标志请求失败的状态, 以及错误信息;
interface ErrorHandling {
  success: boolean;
  error?: {message: string};
}

interface ArtistsData {
 artists: {name: string}[];
}

const handleArtistsResponse = (res: ArtistsData & ErrorHandling) => {
  if(res.error) {
    console.error(response.error.message);
    return;
  }
  console.log(res.artists);
}
联合类型

联合类型实际上是通过操作符|, 将多个类型进行联合, 组成一个复合类型, 当用这个复合类型注解一个变量的时候, 这个变量可以取这个复合类型中任意一个类型

// padLeft 函数的第二个参数只能是 string 或 number 类型
function padLeft(value: string, padding: string | number) {
  // some code
}

// e.g
padLeft('hello', 2); // yes
padLeft('hello', '123'); // yes
padLeft('hello', true); // no
字面量类型
  • 数字字面量

    520这个数, 把它当做类型使用, 它就是数组字面量类型

    let a: 520
    

    当我们初始化这个a变量的时候, 就只能复制520这个数字

    a = 520; // yes
    a = 521; // error: Type '521' is not assignable to type '520'
    
  • 字符串字面量

    字符串字面量也是类似的

    let a: '520';
    
    a = '520';
    a = '521'; // Type '"521"' is not assignable to type '"520"'