前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版

Typescript实践总结 从基础类型到工程化与项目实战

首页2019-09-03 16:25:24Front-End
JavaScriptTypeScript前端工程化

刚上手 TypeScript 那阵子,我最难受的不是语法,而是「编译器为什么要拦我」。明明 let a: void = 'hi' 看着挺正常,它就是报错;明明 arguments 用得好好的,标成 number[] 就红一片。后来我发现,绝大多数卡壳都不是记不住正确写法,而是不知道哪些写法是错的、错在哪一步。

这篇是我把 TypeScript 从基础类型一路啃到工程落地的完整笔记,按「正确的写法 / 错误的写法」成对整理,每个坑都标了 ❌。内容分三块,基础篇讲类型系统本身,工程篇讲声明文件和 tsconfig,实战篇讲 React 和 Vue 里真正会遇到的类型问题。

在本篇文章中,我们将从浅入深,和大家一起学习以下知识:

  • TypeScript 的基本类型体系,以及它比 ES6 多出来的 void、any、never、元组、枚举
  • 接口的四种属性形态(确定、可选、任意、只读)和它们互相之间的约束关系
  • 函数的四种声明方式、重载,以及 interface 和 type 到底该怎么选
  • 泛型、泛型约束、索引类型、映射类型、条件类型这一整条「类型编程」链路
  • 类型推断和四种类型保护(typeof / instanceof / in / 类型谓词)的适用场景
  • 命名空间、三斜线指令、三类库(全局库 / 模块库 / UMD 库)声明文件怎么写
  • tsconfig.json 每一个常用编译选项的含义,以及多项目工程引用怎么配
  • React 函数组件、类组件、高阶组件、Hooks、Redux 的类型写法,和 Vue 的 .vue 声明文件

文章写于 2019 年,当时 TypeScript 还是 3.x,现在已经到 5.x 了。老写法我一律保留原样,需要更新认知的地方我会另起一小段说明现在一般怎么做,具体版本行为以官方文档为准。

# 一、基础篇,类型系统本身

先看这块。基础篇要解决的问题只有一个,把 JavaScript 里那些「运行时才炸」的错误挪到编译期。下面这几张图是我当时整理的思维导图,把基础篇涉及的类型、接口、泛型、类型检查机制串了一遍,可以先扫一眼有个整体印象,后面每一节都是对图里某个分支的展开。

TypeScript 基础篇思维导图 数据类型总览 TypeScript 基础篇思维导图 任意值与类型推论 TypeScript 基础篇思维导图 联合类型与接口 TypeScript 基础篇思维导图 数组与函数类型 TypeScript 基础篇思维导图 类型断言与类型别名 TypeScript 基础篇思维导图 枚举与类 TypeScript 基础篇思维导图 访问修饰符与泛型 TypeScript 基础篇思维导图 类型检查机制 TypeScript 基础篇思维导图 高级类型 TypeScript 基础篇思维导图 常见困惑汇总

# 1.1 基本类型

JavaScript 的类型分为两种,原始数据类型和对象类型。原始数据类型包括布尔值、数值、字符串、null、undefined 以及 ES6 中的新类型 Symbol。这一节主要看前五种原始数据类型在 TypeScript 里怎么标注,布尔值是最基础的一个,用 boolean 定义。

把两份类型清单摆在一起看,差异一眼就出来了。TypeScript 并没有替换掉 JS 的运行时类型,它是在 ES6 那套类型之上又叠了一层「只在编译期存在」的类型,void、any、never、元组、枚举、高级类型都属于这一层。编译完这些标注全部被擦掉,产物还是普通 JS。

ES6数据类型

  • Boolean
  • Number
  • String
  • Array
  • Function
  • Object
  • Symbol
  • undefined
  • null

Typescript数据类型

  • Boolean
  • Number
  • String
  • Array
  • Function
  • Object
  • Symbol
  • undefined
  • null
  • void
  • any
  • never
  • 元组
  • 枚举
  • 高级类型

下面这段把五种原始类型的标注方式一次性过完,重点看 Boolean 和 boolean 的区别,大写开头的是构造函数产出的包装对象,小写的才是原始值类型,这两个混用是新手最常踩的第一个坑。

正确的写法

➖➖➖➖➖➖➖➖➖布尔➖➖➖➖➖➖➖➖➖
// 布尔值
let isDone: boolean = false;  

// 事实上 `new Boolean()` 返回的是一个 `Boolean` 对象
let createdByNewBoolean: Boolean = new Boolean(1);

//(直接调用 `Boolean` 也可以返回一个 `boolean` 类型) 
let createdByBoolean: boolean = Boolean(1); 

➖➖➖➖➖➖➖➖➖数值➖➖➖➖➖➖➖➖➖
// 数值
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;

// ES6 中的二进制表示法
let binaryLiteral: number = 0b1010;

// ES6 中的八进制表示法
let octalLiteral: number = 0o744;
let notANumber: number = NaN;
let infinityNumber: number = Infinity;
➖➖➖➖➖➖➖➖➖字符串➖➖➖➖➖➖➖➖➖
let myName: string = 'Tom';
➖➖➖➖➖➖➖➖➖空值➖➖➖➖➖➖➖➖➖
// 没有返回值的函数为void
function alertName(): void {
    alert('My name is Tom');
}

//声明一个 void 类型的只能将它赋值为 undefined 和 null
let unusable: void = undefined;
➖➖➖➖➖➖➖➖➖Null 和 Undefined➖➖➖➖➖➖➖➖➖
// undefined 类型的变量只能被赋值为 undefined,null 类型的变量只能被赋值为 null
let u: undefined = undefined;
let n: null = null;
@前端进阶之旅: 代码已经复制到剪贴板

正确写法记起来没难度,真正卡人的是下面这些。我整理笔记的时候特意把错误示例单独列一栏,因为编译器报错的时候你需要的是「哦,这个我见过」,而不是重新推一遍类型规则。

错误的写法

注意:正确的很好记,大多数人都会写正确的,关键是要记住这些错误的!!!

➖➖➖➖➖➖➖➖➖布尔➖➖➖➖➖➖➖➖➖
// 注意,使用构造函数 `Boolean` 创造的对象不是布尔值
let createdByNewBoolean: boolean = new Boolean(1);❌

➖➖➖➖➖➖➖➖➖数值➖➖➖➖➖➖➖➖➖
let decLiteral: number = "6";❌

➖➖➖➖➖➖➖➖➖字符串➖➖➖➖➖➖➖➖➖
let myName: string = 999;❌

➖➖➖➖➖➖➖➖➖空值➖➖➖➖➖➖➖➖➖
// 没有返回值的函数为void
function alertName(): void {❌
   return 666;
}
//声明一个 void 类型的只能将它赋值为 undefined 和 null
let unusable: void = 'I love you';❌

➖➖➖➖➖➖➖➖➖Null 和 Undefined➖➖➖➖➖➖➖➖➖
// undefined 类型的变量只能被赋值为 undefined,null 类型的变量只能被赋值为 null
let u: undefined = 888;❌
let n: null = 999;❌
@前端进阶之旅: 代码已经复制到剪贴板

这里有个坑要注意,let unusable: void = null 在默认配置下是允许的,但只要你打开 strictNullChecks,null 就不再是所有类型的子类型,这行立刻变成错误。老项目升级的时候,这一个开关能给你炸出几百个报错,建议单独开一个分支慢慢改,别和业务需求混在一起。

# 1.2 任意值

any 是 TypeScript 给你留的逃生舱。它的语义是「这个位置我不检查了」,所以赋什么值都行,访问什么属性也都不报错。

正确的写法

// 顾名思义,可以被任何值赋值
let anyThing: any = 'hello';
let anyThing: any = 888;
let anyThing: any = true;
let anyThing: any = null;
let anyThing: any = undefined;

// 变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型:
let any;
any =true;
@前端进阶之旅: 代码已经复制到剪贴板

any 用多了 TypeScript 就退化成了 JavaScript,这是老生常谈。我自己的感受是,any 真正危险的地方不在于它自己不检查,而在于它会顺着调用链往外传染:一个返回 any 的函数,它的返回值再往下传三层,这三层全部失去保护,而你在编辑器里看不出任何异常。

顺着上面聊,TS 3.0 之后其实有一个更好的选择叫 unknown。它同样能接住任意值,但你在使用它之前必须先做类型收窄(用 typeof、类型断言或者类型谓词),编译器强制你把「我不知道这是什么」这件事显式处理掉。接口返回值、JSON.parse 的结果、第三方库的回调参数,这几个位置用 unknown 比 any 稳得多。

# 1.3 类型推论

不写类型标注不代表没有类型。TypeScript 会在变量初始化的那一刻,根据右边的值反推一个类型出来钉死,之后再赋别的类型就报错。

正确的写法

// 如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型。
let myFavoriteNumber = 'seven';  
//等价于
let myFavoriteNumber :string= 'seven';
@前端进阶之旅: 代码已经复制到剪贴板

错误的写法

// 第一句已经被推论为String类型了
let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;❌
@前端进阶之旅: 代码已经复制到剪贴板

这里有个细节很多人没注意到:用 let 声明时推断出的是宽泛类型 string,用 const 声明时推断出的是字面量类型 'seven'。前者能再赋别的字符串,后者连别的字符串都不行。这个差异在写联合类型和配置对象的时候会反复咬人,比如 const config = { method: 'GET' } 里 method 推出来是 string 而不是 'GET',传给要求 'GET' | 'POST' 的函数就会报错。老办法是加 as const,TS 4.9 之后还多了 satisfies 这个更精细的写法。

# 1.4 联合类型

一个值可能是字符串也可能是数字,这种场景太常见了。联合类型用竖线连接,语义是「取值可以是这几种类型中的一种」。

正确的写法

// 联合类型(Union Types)表示取值可以为多种类型中的一种。
// 当你允许某个变量被赋值多种类型的时候,使用联合类型,管道符进行连接
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

// 也可用于方法的参数定义, 都有toString方法,访问 string 和 number 的共有属性是没问题的
function getString(something: string | number): string {
    return something.toString();
}
@前端进阶之旅: 代码已经复制到剪贴板

错误的写法

// number类型没有length属性.所以编译错误,因为我们只能访问此联合类型的所有类型里共有的属性或方法:
function getLength(something: string | number): number {❌
    return something.length;
}
@前端进阶之旅: 代码已经复制到剪贴板
fe
  • 一、基础篇,类型系统本身
    • 1.1 基本类型
    • 1.2 任意值
    • 1.3 类型推论
    • 1.4 联合类型
    • 1.5 对象的类型与接口
    • 1.6 数组的类型
    • 1.7 函数的类型
      • 1.7.1 函数相关知识点梳理
    • 1.8 类型断言
    • 1.9 类型别名
    • 1.10 枚举
    • 1.11 类
      • 1.11.1 类与接口的关系
    • 1.12 public private 和 protected
    • 1.13 泛型
      • 1.13.1 泛型函数和接口
      • 1.13.2 泛型类与泛型约束
    • 1.14 类型检查机制
      • 1.14.1 类型推断
      • 1.14.2 类型保护机制
        • 用户自定义的类型保护
        • typeof 类型保护
        • instanceof 类型保护
    • 1.15 高级类型
      • 1.15.1 交叉类型
      • 1.15.2 索引类型
      • 1.15.3 映射类型
      • 1.15.4 条件类型
      • 1.15.5 联合类型
    • 1.16 初学者的困惑
      • 1.16.1 如何优雅的声明类型
      • 1.16.2 类型声明放在哪儿
      • 1.16.3 如何引用外部库
      • 1.16.4 如何组织一个 TS 项目
      • 1.16.5 TSX 和 JSX
      • 1.16.6 变量的 Type 怎么找
      • 1.16.7 常见 Types 之 DOM
      • 1.16.8 常见 Types 之 React
      • 1.16.9 多重 extends
      • 1.16.10 obj[prop] 无法访问怎么办
      • 1.16.11 其他技巧
  • 二、工程篇,声明文件与编译配置
    • 2.1 使用命名空间
    • 2.2 理解声明合并
    • 2.3 如何编写声明文件与引入类库
      • 2.3.1 三种类库声明文件写法
        • 全局库
        • 模块类库
        • UMD类库
      • 2.3.2 两种插件声明文件写法
        • 模块化插件declare module
        • 全局插件declare global
      • 2.3.3 jquery声明文件示例
    • 2.4 配置tsconfig.json
      • 2.4.1 基础配置
      • 2.4.2 工程引用配置多个项目
    • 2.5 编译工具与代码检查
    • 2.6 使用jest进行单元测试
  • 三、项目实战,React 与 Vue
    • 3.1 手动创建 React 项目
    • 3.2 使用脚手架安装
      • 3.2.1 函数组件
      • 3.2.2 类组件
      • 3.2.3 高阶组件
      • 3.2.4 Hooks组件
      • 3.2.5 事件处理与数据请求
      • 3.2.6 列表渲染
      • 3.2.7 Redux与类型
    • 3.3 服务端使用Typescript
    • 3.4 Vue项目实践
  • 总结
  • 参考

← 彻底弄懂 JavaScript 执行机制,Event Loop 宏任务与微任务React 新特性详解,memo、lazy、Suspense 与 Hooks →