前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
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基础及结合React实践(一) 从零搭出可跑的项目

首页2018-12-29 16:30:24Front-End
ReactTypeScript前端工程化

接手一个要用 TypeScript 重写的中后台项目时,我心里想的是「不就是给变量加个冒号吗」。结果第一天就卡在 document.getElementById('btn').style.color = 'blue' 上,编译器红了一整行。真正难受的从来不是语法,是你得开始向编译器解释自己在干什么,而它比你严格得多。

这篇是我从安装 tsc 开始,把基本类型、函数、类、接口、泛型一路啃完,再手写 webpack 配置把 TypeScript 和 React 接起来的完整记录。全程不用脚手架,每一行配置都能看清楚它在解决什么问题。跟完之后你手上会有一个能跑的 TS + React + Redux + React Router 最小项目。

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

  • tsc 的安装、tsc --init 生成的 tsconfig.json 里那几个关键选项在管什么
  • 布尔、数字、字符串、数组、元组、枚举、any、null / undefined、void、never 这一整套基本类型,以及 any / void / never 三者到底差在哪
  • 函数的可选参数、默认参数、剩余参数和函数重载在 TypeScript 里的真实语义
  • 类的定义、继承、public / protected / private 三个修饰符、静态成员和抽象类
  • 接口怎么分别去规范对象、函数、数组和类,以及接口继承接口
  • 泛型在函数和类上的用法,还有「有了接口为什么还要抽象类」
  • 手写 webpack + ts-loader 搭出 TS + React 环境,包括类型定义文件 @types/* 是怎么回事
  • 用 TypeScript 写 React 类组件,给 props 和 state 加接口约束
  • Redux 的 action、reducer、store 全链路类型约束,以及 combineReducers 合并和路由同步到仓库

先说清楚一件事,这篇写于 2018 年底,当时 TypeScript 才 3.2,React 是 16.6,脚手架生态跟现在差得远。原文的写法我一律保留,涉及到现在做法不一样的地方,我会另起一小段说明,具体行为以官方文档为准。这三篇是一个连载,第二篇 Typescript总结篇(二) 把语法细节铺得更全,第三篇 Typescript+React模板搭建(三) 则是带完整截图的工程模板实操。

# 一、装上 tsc,把编译链路先跑通

学 TypeScript 的第一步不是背类型,是先建立一个「改完 .ts 立刻能看到 .js 产物」的循环。看不到产物,你就永远不知道那些类型标注最后到底去哪了。

TypeScript 的编译器叫 tsc,全局装一个就行。

npm i typescript -g
@前端进阶之旅: 代码已经复制到剪贴板

全局安装完成后,我们新建一个hello.ts的ts文件

// hello.ts内容
let a = "poet"
@前端进阶之旅: 代码已经复制到剪贴板

接下来我们在命令行输入tsc hello.ts来编译这个ts文件,然后会在同级目录生成一个编译好了的hello.js文件

// hello.js内容
var a = "poet";
@前端进阶之旅: 代码已经复制到剪贴板

看到这个产物你就明白 TypeScript 干了什么了。let 被降级成了 var(因为默认 target 是 ES3/ES5),类型标注一个不剩,全部被擦掉。TypeScript 的类型只活在编译期,运行时是彻底不存在的,这一点后面讲 never、讲接口的时候会反复用到。

那么我们每次都要输tsc hello.ts命令来编译,这样很麻烦,能否让它自动编译?答案是可以的,使用vscode来开发,需要配置一下vscode就可以。

首先我们在命令行执行tsc --init来生成配置文件,然后我们在目录下看到生成了一个tsconfig.json文件

下面这张就是 tsc --init 之后目录里多出来的 tsconfig.json,它是整个项目类型检查行为的总开关。

执行 tsc --init 后项目根目录生成的 tsconfig.json 文件

这个json文件里有很多选项

  • target是选择编译到什么语法
  • module则是模块类型
  • outDir则是输出目录,可以指定这个参数到指定目录

这三个是最先要动的。target 决定产物语法版本,写 es5 就是给老浏览器用,写 es2018 以上箭头函数、async 都会被原样保留,产物体积小很多。module 决定模块系统,Node 端配 commonjs,走打包器就配 esnext 交给 webpack 自己处理。outDir 单纯是产物落哪个目录。

新版本还多了一个当年没有的 moduleResolution。现在如果你的代码是交给 Vite、webpack、esbuild 这类打包器处理的,官方推荐值是 bundler,它比老的 node 解析策略更贴近打包器的真实行为,比如允许省略扩展名同时又支持 exports 字段。具体取值和兼容矩阵以官方文档为准。

更多细节 https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/tsconfig.json.html

接下来我们需要开启监控了,在vscode任务栏中

有了配置文件还不够,得让它自动跑起来。VS Code 内置了 TypeScript 的构建任务,在任务栏里选 tsc: 监视 就能进入 watch 模式,改完保存自动编译。

VS Code 任务栏中选择 tsc 监视任务开启自动编译

做完这一步你应该看到终端持续输出编译日志,随便在 .ts 里写个类型错误,终端会立刻报出来。如果没有反应,大概率是 tsconfig.json 的 include 没把你的文件圈进去。

Typescript在线编辑器

建议使用在线编辑器练习 http://www.typescriptlang.org/play/index.html

这个 Playground 到现在还在维护,左边写 TS 右边实时出 JS,还能切 TypeScript 版本。学类型的时候比在本地建项目快得多,我到现在验证一个类型推断结果还是习惯先开它。

# 二、数据类型

js是弱类型语言,强弱类语言有什么区别呢?typescript最大的优点就是类型检查,可以帮你检查你定义的类型和赋值的类型。

弱类型的意思是变量的类型可以随时变,let a = 1 之后 a = '1' 完全合法,出错要等到运行时某个方法调不到才暴露。TypeScript 做的事就是在编译期先把这类问题拦下来。下面这一节把常用类型过一遍,每个类型我都补了一句「它到底在什么场景救你」。

# 2.1 布尔类型boolean

下面这段把同一个变量在 JS、TS、Java 三种语言里的写法摆在一起,看的是「类型信息写在哪」。JS 里根本没地方写,TS 是在变量名后面加冒号,Java 是写在变量名前面。

// 在js中,定义isFlag为true,为布尔类型boolean
let isFlag = true;
// 但是我们也可以重新给它赋值为字符串
isFlag = "hello swr";

// 在ts中,定义isFlag为true,为布尔类型boolean
// 在变量名后加冒号和类型,如  :boolean
let isFlag:boolean = true
// 重新赋值到字符串类型会报错
isFlag = "hello swr" 

// 在java中,一般是这样定义,要写变量名也要写类型名
// int a = 10; 
// string name = "poetries"
@前端进阶之旅: 代码已经复制到剪贴板

# 2.2 数字类型number

TypeScript 里没有 int / float / double 之分,所有数字都是 number,跟 JS 的运行时行为完全一致。NaN 和 Infinity 也算 number,这点偶尔会坑人。

let age:number = 28;
age = 29;
@前端进阶之旅: 代码已经复制到剪贴板

# 2.3 字符串类型string

let name:string = "poetries"
name = "iamswr"
@前端进阶之旅: 代码已经复制到剪贴板

这里有个坑要注意。如果你的 tsconfig.json 里 lib 带了 dom,全局作用域下已经存在一个 name 变量(Window.name),在顶层直接写 let name: string 有可能撞上「重复声明」的报错。放在模块里(文件中有任意一个 import 或 export)就没这个问题。我第一次遇到的时候排查了挺久,最后发现只要把文件变成模块就好了。

以上boolean、number、string类型有个共性,就是可以通过typeof来获取到是什么类型,是基本数据类型

那么复杂的数据类型是怎么处理的呢?

# 2.4 数组 Array

数组有两种写法,T[] 和 Array<T>,语义完全一样,选哪个纯看团队约定。真正需要想清楚的是「里面允许放什么」,这决定了你后面取出来的元素能直接调什么方法。

// 数组
// 这是一个字符串数组,只能往里面放字符串,写别的类型会报错
let persion:string[] = ['poetries', 'jing']
// 另一个写法 
let persions:Array<string> = ['poetries', 'jing']

// 如果数组里放对象呢
let persionObject:Array<object> = [{name:'poetries',age:22}]
let persionObjects:object[] = [{name:'poetries',age:22}]

// 在数组中放string、number、boolean、object
let arr:Array<number|object|string|boolean> = [22, 'test', true, {name:'poetries'}]

// 数组中放什么都可以
let arrAny:Array<any> = ['test',12,false]
@前端进阶之旅: 代码已经复制到剪贴板

联合类型的数组 Array<number|object|string> 用起来其实挺别扭,因为取出来的每一项都是联合类型,你想调 .toFixed() 得先做类型收窄。所以真实项目里更常见的是给数组元素定一个接口,而不是堆联合类型。至于 Array<any>,能不写就不写,理由下面 2.7 会讲。

# 2.5 元组类型tuple

元组解决的是这么一类场景:一个数组里每个位置的含义都不同,位置本身就是语义。最典型的就是 React Hooks 里 useState 的返回值,第 0 位是值第 1 位是 setter,这就是元组。

  • 什么是元组类型?其实元组是数组的一种。
  • 有点类似解构赋值,但是又不完全是解构赋值,比如元组类型必须一一对应上
  • 元组类型是一个不可变的数组,长度、类型是不可变的
// 元组类型tuple
// 什么是元组类型?其实元组是数组的一种
let per :[string,number,object] = ['poetries',22,{love: 'coding'}]
@前端进阶之旅: 代码已经复制到剪贴板

原文说元组「不可变」,这个说法要打个补丁。TypeScript 会检查赋值和索引访问,但 per.push('x') 在早期版本里是不拦的,运行时长度照样能变。真想锁死,得用后来才有的 as const 或者 readonly [string, number]。这个我踩过,当时以为编译器保证了不变,结果线上数据多塞了一项都没人发现。

# 2.6 枚举类型enum

什么是枚举?枚举有点类似一一列举,一个一个数出来。一般用于值是某几个固定的值

// 枚举类型enum

enum sex {
    BOY='男孩',
    GIRL='女孩'
}
console.log(sex)
@前端进阶之旅: 代码已经复制到剪贴板
fe
  • 一、装上 tsc,把编译链路先跑通
  • 二、数据类型
    • 2.1 布尔类型boolean
    • 2.2 数字类型number
    • 2.3 字符串类型string
    • 2.4 数组 Array
    • 2.5 元组类型tuple
    • 2.6 枚举类型enum
    • 2.7 任意类型 any
    • 2.8 null undefined类型
    • 2.9 void类型
    • 2.10 never类型
    • 2.11 我们要搞明白any、never、void
  • 三、函数
    • 3.1 函数定义
    • 3.2 函数参数处理
    • 3.3 函数重载
  • 四、类
    • 4.1 定义一个类
    • 4.2 类的继承
    • 4.3 类的修饰符
    • 4.4 静态属性、静态方法
    • 4.5 抽象类
  • 五、接口
    • 5.1 接口规范对象
    • 5.2 接口规范函数
    • 5.3 接口规范数组
    • 5.4 接口规范类
    • 5.5 接口继承接口
  • 六、泛型
    • 6.1 函数的泛型
    • 6.2 类的泛型
  • 七、搭一套 TypeScript + React 的开发环境
    • 7.1 初始化项目
    • 7.2 安装依赖
      • 7.2.1 类型定义文件
      • 7.2.2 相关依赖包
    • 7.3 Typescript config配置
    • 7.4 webpack配置
  • 八、用 TypeScript 写 React 组件
    • 8.1 写一个计数器组件
  • 九、Redux 全链路加上类型约束
    • 9.1 基础使用
    • 9.2 合并reducers
  • 十、路由
    • 10.1 基本用法
    • 10.2 同步路由到redux
  • 总结
  • 参考

← Typescript总结篇(二) 语法全景与工程配置shell入门,从变量语法到能上生产的脚本 →