Skip to content
On this page

Hello TypeScript

一. JavaScript 的类型缺失

  • Javascript 不可否认是一门优秀的语言,随着近几年前端领域的快速发展,让 JavaScript 迅速被普及和受广大开发者的喜爱,借助于 JavaScript 本身的强大,也让使用 JavaScript 开发的人员越来越多

  • 但是 JavaScript也有缺点

    • 由于各种历史因素,JavaScript 语言本身存在很多的缺点;
    • 比如 ES5 以及之前的使用的 var 关键字关于作用域的问题;
    • 比如最初 JavaScript 设计的数组类型并不是连续的内存空间;
    • 比如直到今天 JavaScript 也没有加入类型检测这一机制;
  • JavaScript

    • 不可否认的是,JavaScript 正在慢慢变得越来越好,无论是从底层设计还是应用层面
    • ES6、7、8 等的推出,每次都会让这门语言更加现代、更加安全、更加方便。
    • 但是直到今天,JavaScript在类型检测上依然是毫无进展(为什么类型检测如此重要,后面会聊到)

二. 类型的重要性

类型带来的问题

  • 首先你需要知道,编程开发中我们有一个共识:错误出现的越早越好

    • 能在写代码的时候发现错误,就不要在代码编译时再发现(IDE 的优势就是在代码编写过程中帮助我们发现错误)
    • 能在代码编译期间发现错误,就不要在代码运行期间再发现(类型检测就可以很好的帮助我们做到这一点)
    • 能在开发阶段发现错误,就不要在测试期间发现错误,能在测试期间发现错误,就不要在上线后发现错误。
  • 现在我们想探究的就是如何在代码编译期间发现代码的错误,我们来看下面这段经常可能出现的代码问题。

js
function getLength(str) {
  return str.length;
}

getLength("abc"); // ✅ 正确的调用
getLength(); // ❌ 错误的调用,且ide没有报错
  • JavaScript 和 IDE 都没有给我们报错,结果就是运行时才抛出出错误

image-20230309110504382

  • 这是我们一个非常常见的错误:
    • 这个错误很大的原因就是因为 JavaScript 没有对我们传入的参数进行任何的限制,只能等到运行期间才发现这个错误;
    • 并且当这个错误产生时,会影响后续代码的继续执行,也就是整个项目都因为一个小小的错误而直接崩溃;
  • 当然,你可能会想:我怎么可能犯这样低级的错误呢?
    • 当我们写像我们上面这样的简单的 demo 时,这样的错误很容易避免,并且当出现错误时,也很容易检查出来;
    • 但是当我们开发一个大型项目时呢?你能保证自己一定不会出现这样的问题吗?而且如果我们是调用别人的类库,又如何知道让我们传入的到底是什么样的参数呢?
  • 但是,如果我们可以给 JavaScript 加上很多限制,在开发中就可以很好的避免这样的问题了:
    • 比如我们的 getLength 函数中 str 是一个必传的类型,没有调用者没有传编译期间就会报错;
    • 比如我们要求它的必须是一个String 类型,传入其他类型就直接报错;
    • 那么就可以知道很多的错误问题在编译期间就被发现,而不是等到运行时再去发现和修改;

类型思维的缺失

  • 我们已经简单体会到没有类型检查带来的一些问题,JavaScript 因为从设计之初就没有考虑类型的约束问题,所以造成了前端开发人员关于类型思维的缺失:

    • 前端开发人员通常不关心变量或者参数是什么类型的,如果在必须确定类型时,我们往往需要使用各种判断验证;
    • 从其他方向转到前端的人员,也会因为没有类型约束,而总是担心自己的代码不安全,不够健壮;
  • 所以经常有 JavaScript 不适合开发大型项目 的论调,因为当项目一旦庞大起来,这种宽松的类型约束会带来非常多的安全隐患,多哥人员开发人员之间也没有良好的类型契约

  • 比如当我们去实现一个核心类库时,如果没有类型约束,那么需要对别人传入的参数进行各种验证来保证我们代码的健壮性;

  • 比如我们去调用别人的函数,对方没有对函数进行任何的注释,我们只能去看里面的逻辑来理解这个函数需要传入什么参数,返回值是什么类型;

各种类型方案

  • 为了弥补 JavaScript 类型约束上的缺陷,增加类型约束,很多公司推出了自己的方案:
    • 2014 年,Facebook 推出了flow来对 JavaScript 进行类型检查;
    • 同年,Microsoft 微软也推出了TypeScript1.0版本;
    • 他们都致力于为 JavaScript 提供类型检查;
  • 而现在,无疑TypeScript已经完全胜出:
    • Vue2.x 的时候采用的就是 flow 来做类型检查;
    • Vue3.x 已经全线转向 TypeScript,98.3%使用 TypeScript 进行了重构;
    • 而 Angular 在很早期就使用 TypeScript 进行了项目重构并且需要使用 TypeScript 来进行开发;
    • 而甚至 Facebook 公司一些自己的产品也在使用 TypeScript;
  • 学习 TypeScript 不仅仅可以为我们的代码增加类型约束,而且可以培养我们前端程序员具备类型思维
    • 如果之后想要学习其他语言,比如 Java、Dart 等也会是驾轻就熟;

三. 认识 TypeScript

定义

  • 虽然我们已经知道 TypeScript 是干什么的,也知道它解决了什么样的问题,但是我们还是需要全面的来认识一下 TypeScript 到底是什么?

  • 我们来看一下 TypeScript 在 GitHub 和官方上对自己的定义:

    • GitHub 说法: TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
    • TypeScript 官网:TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.
    • 翻译一下:TypeScript 是拥有类型的 JavaScript 超集,它可以被编译成普通、干净、完整的 JavaScript 代码
  • 怎么理解上面的话呢?

    • 我们可以将 TypeScript 理解成加强版的 JavaScript
    • JavaScript 所拥有的特性,TypeScript 全部都是支持的,并且它紧随 ECMAScript 的标准,所以 ES6、ES7、ES8 等新语法标准,它都是 支持的;
    • TypeScript 在实现新特性的同时总是保持和 ES 标准的同步甚至是领先;
    • 并且在语言层面上,不仅仅增加了类型约束,而且包括一些语法的扩展,比如枚举类型(Enum)、元组类型(Tuple)等;
    • 并且 TypeScript 最终会被编译成 JavaScript 代码,所以你并不需要担心它的兼容性问题,在编译时也可以不借助于 Babel 这样的工具;
  • 所以,我们可以把 TypeScript 理解成更加强大的 JavaScript,不仅让 JavaScript更加安全,而且给它带来了诸多好用的好用特性;

特点

  • 官方对 TypeScript 有几段特点的描述

  • 始于 JavaScript,归于 JavaScript

    • TypeScript 从今天数以百万计的 JavaScript 开发者所熟悉的语法和语义开始;

    • 使用现有的 JavaScript 代码,包括流行的 JavaScript 库,并从 JavaScript 代码中调用 TypeScript 代码;

    • TypeScript 可以编译出纯净、 简洁的 JavaScript 代码,并且可以运行在任何浏览器上、Node.js 环境中和任何支持 ECMAScript 3(或更高版本)的 JavaScript 引擎中;

  • TypeScript 是一个强大的工具,用于构建大型项目

    • 类型允许 JavaScript 开发者在开发 JavaScript 应用程序时使用高效的开发工具和常用操作比如静态检查和代码重构;

    • 类型是可选的,类型推断让一些类型的注释使你的代码的静态验证有很大的不同。类型让你定义软件组件之间的接口和洞察现有 JavaScript 库的行为;

  • 拥有先进的 JavaScript

    • TypeScript 提供最新的和不断发展的 JavaScript 特性,包括那些来自 2015 年的 ECMAScript 和未来的提案中的特性,比如异步功能和 Decorators,以帮助建立健壮的组件;

    • 这些特性为高可信应用程序开发时是可用的,但是会被编译成简洁的 ECMAScript3(或更新版本)的 JavaScript;

大家都在用

  • 正是因为有这些特性,TypeScript 被广泛应用:
    • Angular 源码在很早就使用 TypeScript 来进行了重写,并且开发 Angular 也需要掌握 TypeScript;
    • Vue3 源码也采用了 TypeScript 进行重写,在阅读源码时你会看到大量 TypeScript 的语法;
    • 包括目前已经变成最流行的编辑器 VSCode 也是使用 TypeScript 来完成的;
    • 包括在 React 中的 ant-design 的 UI 库,也大量使用 TypeScript 来编写;
    • 目前公司非常流行 Vue3+TypeScript、React+TypeScript 的开发模式;
    • 包括小程序开发,也是支持 TypeScript 的;

大前端发展趋势

  • 大前端是一群最能或者说最需要折腾的开发者:

    • 客户端开发者:从 Android 到 iOS,或者从 iOS 到 Android,到 RN,甚至现在越来越多的客户端开发者接触前端相关知识 (Vue、React、Angular、小程序);

    • 前端开发者:从 jQuery 到 AngularJS,到三大框架并行:Vue、React、Angular,还有小程序,甚至现在也要接触客户端开 发(比如 RN、Flutter);

    • 目前又面临着不仅仅学习 ES 的特性,还要学习 TypeScript;

    • 新框架的出现,我们又需要学习新框架的特性,比如 vue3.x、react18 等等;

    • 新的工具也是层出不穷,比如 vite(版本更新也很快);

  • 但是每一样技术的出现都会带来惊喜,因为他必然是解决了之前技术的某一个痛点的,而 TypeScript 真是解决了 JavaScript 存在的很多设计缺陷,尤其是关于类型检测的

  • 并且从开发者长远的角度来看,学习 TypeScript 有助于我们前端程序员培养 类型思维,这种思维方式对于完成大型项目尤为重 要。

四. 上手 TypeScript

TypeScript 的编译环境

  • 在前面我们提到过,TypeScript最终会被编译成 JavaScript 来运行,所以我们需要搭建对应的环境:

    • 我们需要在电脑上安装 TypeScript,这样就可以通过TypeScript Compiler将其编译成 JavaScript;
  • 所以,我们需要先可以先进行全局的安装:

bash
# 安装命令
npm install typescript -g
# 查看版本
tsc --version
  • 使用 tsc xxx.ts 可以编译生成 xxx.js 文件,如果可以使用 html 引入或者 node 运行的方式使用编译生成的 js 代码

TypeScript 的运行环境

  • 如果我们每次为了查看 TypeScript 代码的运行效果,都通过经过下面两个步骤的话就太繁琐了:

    • 第一步:通过 tsc 编译 TypeScript 到 JavaScript 代码;
    • 第二步:在浏览器或者 Node 环境下运行 JavaScript 代码;
  • 是否可以简化这样的步骤呢?

    • 如编写了 TypeScript 之后可以直接运行在浏览器上?

    • 比如编写了 TypeScript 之后,直接通过 node 的命令来执行?

  • 可以分别通过两个解决方案来完成:

    • 通过 webpack,配置本地的 TypeScript 编译环境和开启一个本地服务,可以直接运行在浏览器上;
    • 方式二:通过 ts-node 库,为 TypeScript 的运行提供执行环境;
bash
# 安装ts-node本体
npm install ts-node -g
# ts-node依赖tslib和@types/node两个包
npm install tslib @types/node -g
# 现在,我们可以直接通过 ts-node 来运行TypeScript的代码
ts-node math.ts

变量的声明

  • 和 JavaScript 不一样,TypeScript 里的定义变量需要指定类型
  • 完整的声明格式如下
    • 声明了类型后TypeScript 就会进行类型检测,声明的类型可以称之为类型注解(Type Annotation)
typescript
var/let/const 标识符: 数据类型 = 要赋的值
  • 我们声明一个字符串类型的 message,完整的写法如下:
typescript
let message: string = "Hello World";
  • 当然也可以先定义变量和类型,然后再进行赋值
typescript
let message: string;
message = "Hello World";
  • 注意:这里的 string 是小写的,和 String 是有区别的
    • string 是 TypeScript 中定义的字符串类型,String 对应的是字符串包装类而不是基本数据类型
    • 如果真想用 String,正确的用法应该如下
typescript
let message: String = new String("Avoid newing things where possible");
let message: string = "A string, in TypeScript of type 'string'";

Don’t ever use the types Number, String, Boolean, Symbol, or Object These types refer to non-primitive boxed objects that are almost never used appropriately in JavaScript code.

typescript
/* WRONG */
function reverse(s: String): String;

Do use the types number, string, boolean, and symbol.

typescript
/* OK */
function reverse(s: string): string;
  • message 被已经指定为字符串类型,如果再给它赋其他类型的值,就会报错

image-20230309144441722

声明变量的关键字

  • 在 TypeScript 定义变量(标识符)和 ES6 之后一致,可以使用 var、let、const 来定义

  • 当然,在 typescript-eslint 中并不推荐使用 var 来声明变量

    • 在 TypeScript 中并不建议再使用 var 关键字了,主要原因和 ES6 升级后 let 和 var 的区别是一样的,var 是没有块级作用域 的,会引起很多的问题,这里不再展开探讨。

image-20230309144805082

无法声明重名变量?

  • 即使是两个不同的文件,里面定义了同一个名字的变量也会报错
  • 因为 ts 默认在顶层声明的都是全局变量,默认全局可访问,可能会和环境里其他文件的变量冲突

image-20230309205456270

  • 我们可以通过空导出来开启模块化(后面会再详细介绍),这样就只能在文件内访问了,暂时规避下报错
typescript
const a = 2;
export {};

变量的类型推导(推断)

  • 在开发中,有时候为了方便起见我们并不会在声明每一个变量时都写上对应的数据类型,我们更希望可以通过 TypeScript 本身的 特性帮助我们推断出对应的变量类型,实际上也确实可以
typescript
let message = "Hello World";
  • 如果我们再给 message 赋值为 123

image-20230309150859226

  • 这是因为在一个变量第一次赋值时,会根据后面的赋值内容的类型,来推断出变量的类型:
  • 上面的 message 就是因为后面赋值的是一个 string 类型,所以 message 虽然没有明确的说明,但是实际上会被推断为一个 string 类型,因此后面当然就不能再修改类型了

image-20230309145950079

JavaScript 和 TypeScript 的数据类型

  • 我们经常说TypeScript 是 JavaScript 的一个超集,JavaScript 和 TypeScript 是有一部分重合的类型的

image-20230309150026933

五. TS 和 JS 都有的数据类型

number 类型

  • 数字类型是我们开发中经常使用的类型,TypeScript 和 JavaScript 一样,不区分整数类型(int)和浮点型(double),统一为 number 类型
typescript
let num = 100;
num = 20;
num = 6.66;
  • 学习过 ES6 应该知道,ES6 新增了二进制和八进制的表示方法,而 TypeScript 也是支持二进制、八进制、十六进制的表示
typescript
num = 100; // 十进制
num = 0b110; // 二进制
num = 0o555; // 八进制
num = 0xf23; // 十六进制

boolean 类型

boolean 类型只有两个取值:truefalse,非常简单

typescript
let flag: boolean = true;
flag = false;
flag = 20 > 30;

string 类型

  • string 类型是字符串类型,可以使用单引号或者双引号表示:
typescript
let message: string = "Hello World";
message = "Hello TypeScript";
  • 同时也支持 ES6 的模板字符串来拼接变量和字符串
typescript
const name = "why";
const age = 18;
const height = 1.88;

const info = `my name is ${name}, age is ${age}, height is ${height}`;
console.log(info);

字面量类型

  • 字面量类型不算 JS 的基本类型,但是却处处可见字面量形式的变量声明,所以放到了这里
  • 我们上面用 let 来定义数字和字符串类型,如果我们用 const 定义数字和字符串类型,类型会被推断为对应的字面量,而不是字符串或者是数字(下称通用类型),
    • 因为这是考虑声明的是常量,后续不会再修改

image-20230309200651602

  • 当然我们也可以显式把 let/var 定义的变量指定为字面量类型

image-20230309204146535

  • 拥有一个只能有一个值的变量并没有多大用处,因为他的值只能是它自身,但是如果组成联合类型就非常有用,后面会学到,这样就可以指定值只能是给定的几个中的一个

  • 还有一种文字类型:boolean 类型,本质上是 true 和 false 的 联合类型

  • 可以和其他类型结合起来,比如对象等,后面会再介绍

  • 总结一下:对于数字、字符串和 boolean 类型,let 声明会被推断为通用类型,const 声明会被推断为对应的字面量类型

Array 类型

  • 可以用两种方式进行定义
typescript
const names: string[] = ["abc", "bca", "cba"];
const names2: Array<string> = ["abc", "bca", "cba"]; // 实际上是一种泛型的语法,后面会介绍

names.push("why");
names2.push("why");
  • 实例的两种方法都是定义了一个数组,且数组里面只存放字符串类型,如果添加其他类型进入上面的数组,就会报错

image-20230309210036527

  • 注意:实际上是可以使用联合类型使数组可以存放多个类型的子元素的,但真实开发中,数组一般要存放相同的数据类型,这也是为了方便使用
typescript
const arr: (string | number)[] = [1, "2", 3];
  • ts 可以自动推断出数组类型,注意 const 不会推断出字面量形式的数组类型

image-20230309212019012

  • 也可以指定为对应的字面量类型

image-20230309212047360

Object 类型

  • object 对象类型可以用于描述一个对象
  • 如果直接把 object 拿来当做类型使用,定义的时候不会报错,但是后续使用时无法访问我们定义在其中的任何属性,可以简单的理解为是一个空对象

image-20230309213226557

  • 类型还可以是 Object{},定义时均不会报错,前者官方文档不推荐使用,后者是空对象字面量
  • 上述的三者都不包含定义的属性信息,均无法访问到定义的属性
  • 一般定义对象类型,我们会采取的方式是,列出其属性及其类型
    • 注意类型部分的每行的结尾可以是 ;,不写
    • 后面我们会用到类型别名将一长串的类型注解写到其他地方去
typescript
const obj: {
  name: string;
  height: number;
  age: number;
} = {
  name: "why",
  height: 1.88,
  age: 18,
};
  • 这种定义 + 直接赋值的声明方式,类型里必须列出对象里包含的所有属性不能多也不能少,后面会提到这是严格字面量检测

  • 这样太麻烦,所以我们一般使用自动推断出对象类型,且 var/let/const 推断出的类型一致,推断出来的属性是通用类型

image-20230309215004052

  • 字面量形式的对象类型如下
typescript
const obj: {
  name: "why";
  height: 1.88;
  age: 18;
} = {
  name: "why",
  height: 1.88,
  age: 18,
};

Symbool 类型

  • 和 JS 中的完全一致,一般用类型推导或者指明
  • 在 ES5 中,如果我们是不可以在对象中添加相同的属性名称的,比如下面的做法:
typescript
const person = {
  identity: "程序员",
  identity: "老师", // ❌ 对象文本不能具有多个名称相同的属性。
};
  • 通常我们的做法是定义两个不同的属性名字:比如 identity1 和 identity2
  • 但是我们也可以通过symbol来定义相同的名称,因为 Symbol 函数返回的是不同的值:
typescript
const s1 = Symbol("title");
const s2: symbol = Symbol("title");
const person = {
  [s1]: "程序员",
  [s2]: "老师",
};

null 和 undefined 类型

  • JavaScript中,undefinednull 是两个基本数据类型
  • TypeScript中,它们各自的类型也是 undefinednull,也就意味着它们既是实际的值,也是自己的类型
typescript
const a = null;
let b: undefined = undefined;
  • 默认情况下nullundefined是所有类型的子类型。 就是说你可以把nullundefined赋值给number类型的变量
    • 但是这是之前的行为表现,TypeScript 2.0 增加了一种新的严格空值检查模式,他提供了 strictNullChecks 的选项,如果为 true,则不允许把null 和 undefined赋值给其他类型的变量
    • 在 vscode 内部有对应的配置项,默认是 true,也就是默认不允许此行为
    • tsconfig.json 里关闭这一选项就可以复现把nullundefined赋值给number类型的变量不报错的现象,但是建议开启
json
{
  "strictNullChecks": true
}

函数类型:参数和返回值

  • 函数是 JavaScript 非常重要的组成部分,TypeScript 允许我们指定函数的参数和返回值的类型。
  • 声明函数时需要每个参数后添加类型注解,以声明函数接受的参数类型
typescript
function greet(name: string) {
  console.log("Hello" + name.toUpperCase());
}

// ❌ Argument of type:'number' is not assignable to parameter of type 'string'
greet(123);

// ❌ Expected 1 arguments, but got 2
greet("abc", "cba");
  • 我们也可以添加返回值的类型注解,这个注解出现在函数列表的后面
typescript
function sum(num1: number, num2: number): number {
  return num1 + num2;
}
  • 和变量的类型注解一样,我们通常情况下不需要返回类型注解,因为 TypeScript 会根据 return 返回值推断函数的返回类型:
    • 某些第三方库处于方便理解,会明确指定返回类型,看个人喜好;

image-20230310101644731

函数类型:匿名函数的参数

  • 匿名函数与函数声明会有一些不同
    • 当一个函数出现在 TypeScript 可以确定该函数会被如何调用的地方时;
    • 该函数的参数会自动指定类型;

image-20230310111241038

  • 我们并没有指定 item 的类型,但是 item 是一个string类型:
    • 这是因为 TypeScript 会根据forEach 函数的类型以及数组的类型推断出 item 的类型;
    • 这个过程称之为上下文类型(contextual typing),因为函数执行的上下文可以帮助确定参数和返回值的类型

函数类型:接收对象类型参数

  • 如果我们希望限定一个函数接受的参数是一个对象,这个时候要如何限定呢?
    • 我们可以使用对象类型;
typescript
function printCoordinate(point: { x: number; y: number }) {
  console.log("x坐标:", point.x);
  console.log("y坐标:", point.y);
}
printCoordinate({ x: 10, y: 30 });
  • 在指定对象参数类型的时候,对象的属性必须是严格匹配且不能多或者少
    • 但是其实这里有个神奇的现象,如果先定义一个除了有对应的 x、y 属性,还有其他属性的对象,再把对象传入函数,并不会报错,只有我们将对象字面量直接传入才会报错
    • 其实这也是前面出现的严格字面量检测,后面会再详细说到
typescript
const obj = {
  x: 1,
  y: 2,
  z: 3,
};
printCoordinate({
  x: 1,
  y: 2,
  z: 3,
}); // ❌ 报错
printCoordinate(obj); // ✅ 不会报错
  • 利用上面的特性,一个应用场景是,我们可以限制传入的参数为数组、字符串或者是含有 length 属性的对象
    • 字符串和数组只有 length 属性,因此可以字面量类型直接传入
    • 含有其他属性的对象则不行

image-20230310120628452

  • 对象类型也可以指定哪些属性是可选的,可以在属性的后面,冒号的前面添加一个?
typescript
function printCoordinate(point: { x?: number; y: number }) {
  console.log("x坐标:", point.x?.toFixed());
  if (point.x) {
    // 用if判断进行「类型缩小」
    console.log("x坐标:", point.x.toFixed());
  }
}
printCoordinate({ x: 10, y: 30 });
  • 因为不一定有,所以不能访问可选属性的属性,要使用可选链,或者进行判断来类型缩小,后面会继续介绍此概念

六. TS 中特有的数据类型

any 类型

  • 在某些情况下,我们确实无法确定一个变量的类型,并且可能它会发生一些变化,这个时候我们可以使用any类型(类似于 Dart 语言中的 dynamic 类型)

  • 实际上不表明属性的时候,除了能自动推导的情况,其他时候属性自动就是any比如函数参数不表明类型的时候

  • any 类型有点像一种讨巧的 TypeScript 手段,让我们能 回到 JavaScript

    • 我们可以对 any 类型的变量进行任何的操作,包括获取不存在的属性、方法;
    • 我们给一个 any 类型的变量赋值任何的值,比如数字、字符串的值;
typescript
let a: any = "why";
a = 123;
a = true;

const arr: any[] = ["why", 18, 1.88];
  • 如果对于某些情况的处理过于繁琐不希望添加规定的类型注解,或者在引入一些第三方库时,缺失了类型注解,这个时候我们可以使用 any
    • 包括在 Vue 源码中,也会使用到 any 来进行某些类型的适配;

unknown 类型

  • unknown 是 TypeScript 中比较特殊的一种类型,它用于描述类型不确定的变量
  • 和 any 类型有点类似,但是unknown 类型的值上做任何事情都是不合法的;
  • 比如进行类型的校验(类型缩小),才能根据缩小后的类型进行对应的操作

image-20230310135914669

void 类型

  • void 通常用来指定一个函数是没有返回值的,那么它的返回值就是 void 类型,这个函数我们没有写任何类型,那么它默认返回值的类型就是 void 的

image-20230310154906175

  • 我们也可以显式的来指定返回值是 void:
typescript
function foo(num1: number, num2: number): void {
  console.log(num1 + num2);
}
  • 这里还有一个注意事项: 返回值是 void 类型的时候,函数也可以显式的返回 undefined
typescript
function foo(num1: number, num2: number): void {
  return undefined;
}
  • 基于上下文的类型推导(Contextual Typing) 推导出函数返回类型为 void 的时候,并不会强制函数一定不能返回内容
    • 比如 forEach 函数中的函数参数返回值是 void 类型,我们传入的函数推导出来也应该是 void 返回值,但是实际上它可以有返回值
    • 这也是为了让 ts 更加灵活
typescript
const names = ["abc", "cba", "nba"];
names.forEach((item) => item.length); // ✅ 无报错

// 我们自己也可以编写个高阶函数
function foo(fn: () => void) {
  fn();
}

foo(() => ({ name: "why" })); // ✅ 无报错

never 类型

  • never 表示永远不会发生值的类型,比如一个函数:
    • 如果一个函数中是一个死循环或者抛出一个异常,那么这个函数会返回东西吗?
    • 不会,我们永远无法到达返回的时候,那么写 void 类型或者其他类型作为返回值类型都不合适,我们就可以使用 never 类型;
    • 上面说的情况实测不一定会正确的类型推断,可以手动标注 never 类型
typescript
function foo(): never {
  while (true) {
    console.log(11);
  }
}
function bar(): never {
  throw new Error("123");
}
  • 实际开发中一般 never 是类型推导出来的,很少用它
  • never 类型的值的属性无法被访问
  • never 还会在联合类型不可能出现的情况的时候被推断出来
    • 下面的联合类型意思是 param 参数只会是 number 和 string 中的一种,不可能再是其他类型,因此不会进入最后一个 if 的代码块内,则代码块里的 param 转化为了 never 类型

image-20230310161114476

  • 下面这种情况也会推断 never 类型
    • 看起来很奇怪,为什么联合类型上面可以,下面连符合联合类型的类型判断都会变成 never?
    • 和函数参数不一样,num 的类型在进入第一个判断前已经被隐式的收窄为 number 类型了,所以其实同理于联合类型,num 不可能再为 string 类型了

image-20230310162404257

  • 尤玉溪解释了 never 类型的一个用法
typescript
function foo(val: number | string | boolean) {
  switch (typeof val) {
    case 'number':
      // 这里 val 被收窄为 number
      break
    case 'string':
      // val 在这里是 string
      break
   	case 'boolean':
  		// val 在这里是 boolean
  		break
    default:
      // val 在这里是 never
      const exhaustiveCheck: never = val
      break
  }
}

// 如果有一天联合类型被增加
function foo(val: number | string | boolean | null) {
  // ...
  default:
    // val 在这里是 null
    const exhaustiveCheck: never = val // ❌ 不能将null赋值给never
    break
}

tuple 类型

  • tuple 是元组类型,很多语言中也有这种数据类型,比如 Python、Swift 等。
typescript
const tInfo: [string, number, number] = ["why", 18, 1.88];
  • 需要手动声明,否则可能会被推导为元素是联合类型的数组类型

  • 那么 tuple 和数组有什么区别呢?

    • 首先,数组中通常建议存放相同类型的元素,不同类型的元素是不推荐放在数组中。(可以放在对象或者元组中)
    • 其次,元组中每个元素都有自己特性的类型,根据索引值获取到的值可以确定对应的类型,如果是联合类型元素的数组,取到的每个元素都是联合类型,必须进行类型缩小才能进行对应类型的操作
  • 那么 tuple 在什么地方使用的是最多的呢?

    • tuple 通常可以作为返回的值,在使用的时候会非常的方便;
    • 下面实现一个玩具版的 useState,setState 会被推断出函数类型,如果不标注 tuple 类型,myUseState 的返回值就是 any[]

image-20230310164250834

enum

枚举值,后面会再介绍

Comments
  • Latest
  • Oldest
  • Hottest
Powered by Waline v2.14.9