TypeScript 才有的型别讲解

591 阅读4分钟

前言:根据资料类型来学习

  1. (已讲完)原始资料型别(Primitive types) : string (字串)、number (数值)、boolean (布林值)、null、undefined
  • string (字串)、number (数值)、boolean (布林值)没什么好讲的,跟JavaScript一样~~
  • null、undefined可以看juejin.cn/post/709521…
  1. (这期讲)TypeScript 才有的型别: any、unknown、void、 never、 union types (联合型别) 、intersection types(交集型别)、 literal types (字面值型别)、 tuple (元组)、 enums (列举)
  • any、unknown、 never、可以看juejin.cn/post/709521…

  • 讲一下剩下的union types (联合型别) 、intersection types(交集型别)、 literal types (字面值型别)、 tuple (元组)、 enums (列举)

  1. (下期讲)物件型别(Object types): object (物件) 、 arrays (阵列) 、function (函式)

union types=‘或’,intersection types=‘与’

function printId2(id: number | string) {
    if (typeof id === "string") {
        //型别为字串才 toUpperCase
        console.log(id.toUpperCase());
    } else {
        //其他自动判定为 number 型别
        console.log(id);
    }
}
printId2("ABC");

union types实际开发中多与narrowing一起使用,下面讲一下narrowing.....

union types好朋友:narrowing把多个型别缩小为一个

1.使用 typeof型别保护 

typeof 的操作可以让我们取得值的型别类型,如stringnumberbooleansymbolundefinedobjectfunction。但 null 除外。

如下例子,typeof strs === "object"这就是一种 type guards (型别保护)。虽然 null 无法使用 typeof 来取得型别,但 null 也是 object, TypeScript 会提醒 strs 有可能是 null。(tsconfig 记得打开严谨模式)

function printAll(strs: string | string[] | null) {
    if (typeof strs === "object") {
      for (const s of strs) {
       //Object is possibly 'null'.
      }
    } else if (typeof strs === "string") {
      console.log(strs);
    } else {
      console.log(strs);
    }
}

2.使用 switch 或是===!====, and!=来缩小型别

function printAll(strs: string | string[] | null) {
  if (strs !== null) {
    if (typeof strs === "object") {
      for (const s of strs) {
        console.log(s);
      }
    } else if (typeof strs === "string") {
      console.log(strs);
    }
  }
}

3.使用"value" in x, value 为 string literal,x 为联合型别

type Fish = { swim: () => void };
type Bird = { fly: () => void };
 
function move(animal: Fish | Bird) {
  if ("swim" in animal) {  //animal 物件里有属性 "swim",则一定是 Fish 型别
    return animal.swim();
  }
 
  return animal.fly();
}

4.使用instanceof检查某个值是否为某个 constructor

function logValue(x: Date | string) {
  if (x instanceof Date) { 
    console.log(x.toUTCString());       
    //(parameter) x: Date 
  } else {
    console.log(x.toUpperCase());
    //(parameter) x: string
  }
}

5.使用 Assignments

let x = Math.random() < 0.5 ? 10 : "hello world!";
console.log(Math.random());
console.log(x);
x = 1; 
console.log(x); //1
x = true;  //error:Type 'boolean' is not assignable to type 'string | number'.

6. 定义一个函式,这个函式返回的值就是 type predicates

  • 函式最后会回传 true 或 false,如果是 true,表示符合该变数符合该 type predicate。
type Fish = { swim: () => void };
type Bird = { fly: () => void };

//pet 有可能是 Fish or Bird 的型别
//isFish 这个function 回传值的型别为 : pet is Fish,看是回传 true or false
//当返回了true, 那他就是Fish

function isFish(pet: Fish | Bird): pet is Fish { 
  return (pet as Fish).swim !== undefined;   //(pet as Fish)断言 pet 为 Fish 型别 , 所以 swim 不会是undefined 
}

7.Discriminated unions (可辨识联合)

TypeScript 可以针对相同的可识别的属性来自动判断型别,再去执行不同情境的操作,他有 3 个要素:

  1. 每个 interface 需要有相同的可辨识的属性,如status
  2. 使用 type alias 宣告联合型别,他包含了哪些型别, 如type Resp = ISuccessResp | IErrorResp;
  3. 使用 type guard, 如resp.status === true

用例:可用在后端回传 response

  • status 是 OK 的话,则资料会带有 payload,否则资料会带有 error。我们可以自行定义多个不同的 Type,这些 Type 有共同的栏位, 如status,那就用这个栏位来判断还有哪些属性。

  • 最后没有判断到的情况可以利用 exhaustiveness checking, 用 never 型别表示不应该存在的状态,一般用于错误处理。

interface ISuccessResp {
  status: 'OK';  //相同栏位 status
  payload: unknown;
}

interface IErrorResp {
  status: 'ERROR';
  errorCode: number;
  description: string;
}

type Resp = ISuccessResp | IErrorResp;

const parseResponse = (resp: Resp) => {
  switch (resp.status) {
    case 'OK':  //透过 narrow 知道是 ISuccessResp 型别
      return resp.payload; //则去使用ISuccessResp的属性payload
    case 'ERROR': // 透过 narrow 知道是 IErrorResp 型别
      return resp.description; //则去使用IErrorResp的属性payload
    default:
      const _exhaustiveCheck: never = resp; 
      return _exhaustiveCheck;
  }
};

Literal Types 字面值型别: 值当作 "型别" 来使用

Literal Types 字面值型别: "值" 可以当作 "型别" 来使用,用来约束取值只能是某几个中的一个。

function printText(s: string, alignment: "left" | "right" | "center") {
    console.log(`${s} placed at the ${alignment}`)
}
printText("Hello, world", "left");

function compare(a: string, b: string): -1 | 0 | 1 {
    return a === b ? 0 : a > b ? 1 : -1;
}

//String Literal Type
type Index = 'a' | 'b' | 'c';

// Mapped Types 
type FromIndex = {
    [k in Index]?: number
};
const bad: FromIndex = {
    b: 1,
    c: 2,
    d: 3
    //Object literal may only specify known properties, and 'd' does not exist in type 'FromIndex'.
};

列举(Enums)型别用于取值被限定在一定范围内

列举(Enums)型别用于取值被限定在一定范围内的场景,可以用来管理多个同系列的常数,作为状态判断使用。比如一周只能有七天,颜色限定为红绿蓝等。也可以自定义来表示每一个值。

列举项有两种型别:常数项(constant member)和计算所得项(computed member)

  • 像是二元运算子<<、 |、&等都归为常数项,常数项会在编译阶段被删除,并且不能包含 computed member (计算成员), 只能是常数项(constant member)。
enum Direction {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT",
}
  • 其他则为计算所得项,"blue".length就是一个计算所得项。:
enum Color { Red, Green, Blue = "blue".length };
console.log(Color.Blue); //4
  • 外部列举(Ambient Enums)是使用 declare enum 定义的列举型别。declare 定义的型别只会用于编译时的检查,编译结果中会被删除。

declare enum Directions {
    Up,
    Down,
    Left,
    Right
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
// var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

Tuple 就是合并了不同型别的物件

//直接对元组型别的变数进行初始化或者赋值,需提供所有项目。如果要新增不同型别的项目也是无法的:
let tom: [string, number];
tom = ['tom', 18]; //如果只有宣告tom沒賦值,會是undefined,tsconfig strictNullChecks 打開的話會報錯提醒
tom[0] = 'Tom'; //ok
tom[1] = 25; //ok
tom[0].slice(1); //ok
tom[1].toFixed(2); //ok
tom.push('male'); //ok