你需要知道 TypeScript 的那些事<二>

644 阅读5分钟

你需要知道 TypeScript 的那些事<二>

写在前面:本次 ts 系列文章共4篇,从入门到"入土"系列,本文为第二篇,且本文仅作为学习笔记。

你若盛开,清风自来。想要获取更多的财富,就要让自己变得更有价值。

上回说到:TS 是 JS 的超集(包含关系);TS 中对于变量、函数接收参数和返回参数等都有严格的类型判断;TS中 type 类型别名interface 接口 的区别,大体上没有区别,但是在扩展、继承等方面还是有些差异;详情请查看:第一篇链接

本文内容

  • 类型保护
    • 类型断言 as
    • 类型收窄 in
    • 类型收窄 typeof
    • 类型收窄 instanceof 只能用在类上
  • 常用类型
    • 文字类型
    • 枚举类型 enmu
    • never 类型

本文阅读时间大约为15分钟,请听笔者娓娓道来。

类型保护

有时候你会得到一个值的类型信息,而 typescript 是不能知道的。

// 声明两个接口Waiter(服务员)接口和Teacher(技师)接口,然后在写一个judgeWho(判断是谁)的方法,里边传入一个animal(任意值),这时候可以能是Waiter,也可能是Teacher。所以我们使用了联合类型,关键符号是|(竖线)
interface Waiter {
  jiedai: boolean;
  say: () => {};
}
interface Teacher {
  anjiao: boolean;
  skill: () => {};
}
function judgeWho(animal: Waiter | Teacher) {
  animal.say(); // 这样写会报错 因为 judgeWho 不能准确的判断联合类型具体的实例是什么
}

类型断言 - as

类型断言就是通过断言的方式确定传递过来的准确值

// 这时候就需要再引出一个概念叫做类型保护
// 类型保护之类型断言:类型断言就是通过断言的方式确定传递过来的准确值
function judgeWho(animal: Waiter | Teacher) {
  if (animal.anjiao) {
    (animal as Teacher).skill();
  }else{
    (animal as Waiter).say();
  }
}

与类型注释一样,类型断言由编译器移除,不会影响代码的运行时行为。

类型收窄 - in

function judgeWhoTwo(animal: Waiter | Teacher) {
  if ("skill" in animal) {
    animal.skill();
  } else {
    animal.say();
  }
}

类型收窄 - typeof

function add(first: string | number, second: string | number) {
  return first + second;
}
function add(first: string | number, second: string | number) {
  if (typeof first === "string" || typeof second === "string") {
    return `${first}${second}`;
  }
  return first + second;
}

类型收窄 - instanceof

// 类型保护之 instanceof 只能用在类上
class NumberObj {
  count: number;
}
function addObj(first: object | NumberObj, second: object | NumberObj) {
  if (first instanceof NumberObj && second instanceof NumberObj) {
    return first.count + second.count;
  }
  return 0;
}

常用类型

目的:收录项目中常用类型,后期会做补充。

文字类型

单独的使用文字类型并不是很有价值(比鸡肋还鸡肋)。

举个🌰

// x 的类型为 hello,表示值也只能是 hello,其它就会报错
let x: "hello" = "hello";

x = "howdy";
// Type '"howdy"' is not assignable to type '"hello"'.

但是,通过将文字组合成联合,可以表达一个更有用的概念。

举个🌰

function printText(s: string, alignment: "left" | "right" | "center") {
    // ...
}

printText("Hello, world", "left");
printText("G'day, mate", "centre");
// Argument of type '"centre"' is not assignable to parameter of type '"left" | "right" | "center"'.

这样就做到了参数的限制,同样也可以限制返回值。

举个🌰

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

枚举类型

枚举允许开发人员定义一组命名的常量。使用枚举可以更容易地记录意图,或者创建一组不同的案例。

举个🌰

// 定义了一个 Status 的枚举类型
enum Status {
  SUCCESS,
  FAIL,
  WARN,
}

聪明的脑袋瓜子里是否在想,为啥要用 enum ? enum 有什么好处?

  • 枚举可以让输入变得更简单
  • 更容易记录意图

举个🌰

// 初级程序员写法:
function getStatus(status: number) {
  if (status === 0) {
    return "SUCCESS";
  } else if (status === 1) {
    return "FAIL";
  } else if (status === 2) {
    return "WARN";
  }
}
const result = getStatus(0);
console.log(`状态为${result}`);
// 中级程序员写法:
const Status = {
  MASSAGE: 0,
  SPA: 1,
  DABAOJIAN: 2,
};
function getStatus(status: any) {
  if (status === Status.SUCCESS) {
    return "SUCCESS";
  } else if (status === Status.FAIL) {
    return "FAIL";
  } else if (status === Status.WARN) {
    return "WARN";
  }
}

const result = getStatus(Status.SUCCESS);
console.log(`状态为${result}`);

写代码的目标是,让代码不要成为屎山。接下来,咱们用枚举来改写。

举个🌰

// 高级程序员写法:
enum Status {
  SUCCESS,
  FAIL,
  WARN,
}
function getStatus(status: any) {
  if (status === Status.SUCCESS) {
    return "SUCCESS";
  } else if (status === Status.FAIL) {
    return "FAIL";
  } else if (status === Status.WARN) {
    return "WARN";
  }
}
// const result = getServe(Status.SUCCESS);
// console.log(`状态为${result}`);
// 改写成枚举类型对应值
const result = getServe(1);
// 这看起来很神奇,这是因为枚举类型是有对应的数字值的,默认是从 0 开始的。我们直接用console.log()就可以看出来了。
console.log(Status.SUCCESS); // 0
console.log(Status.FAIL); // 1
console.log(Status.WARN); // 2
// 那这时候不想默认从 0 开始,而是想从 1 开始。可以这样写。
enum Status {
  SUCCESS = 1,
  FAIL,
  WARN,
}

never 类型

never 类型是 TypeScript 中的底层类型。它自然被分配的一些例子:

  • 一个从来不会有返回值的函数(如:如果函数内含有 while(true) {});
  • 一个总是会抛出错误的函数(如:function foo() { throw new Error('Not Implemented') }foo 的返回类型是 never);

举个🌰

let foo: never; // ok
let foo: never = 123; // Error: number 类型不能赋值给 never 类型

// 作为函数返回类型的 never
let bar: never = (() => {
  throw new Error('Throw my hands in the air like I just dont care');
})();

除了以上方式,还可以用作详细的检查。

举个🌰

function foo(x: string | number): boolean {
  if (typeof x === 'string') {
    return true;
  } else if (typeof x === 'number') {
    return false;
  }

  // 如果不是一个 never 类型,这会报错:
  // - 不是所有条件都有返回值 (严格模式下)
  // - 或者检查到无法访问的代码
  // 但是由于 TypeScript 理解 `fail` 函数返回为 `never` 类型
  // 它可以让你调用它,因为你可能会在运行时用它来做安全或者详细的检查。
  return fail('Unexhaustive');
}

function fail(message: string): never {
  throw new Error(message);

与 void 的差异

never 表示一个从来不会优雅的返回的函数时,你可能马上就会想到与此类似的 void,然而实际上,void 表示没有任何类型,never 表示永远不存在的值的类型。

当一个函数返回空值时,它的返回值为 void 类型,但是,当一个函数永不返回时(或者总是抛出错误),它的返回值为 never 类型。void 类型可以被赋值(在 strictNullChecking 为 false 时),但是除了 never 本身以外,其他任何类型不能赋值给 never。

总结

本文主要讲述了类型保护中 as、in、typeof、interface 的一些用法以及常用的类型如文字类型、枚举类型和 never 类型。如有不当之处,请不吝赐教。最后,笔者近期加入了 mini-vue 作者创建的 ts 学习小组,共同学习。有兴趣的话,拉你入群。之后还会有多种形式的对赌学习哦!