title: typescript学习(一)categories: TS
tag: TypeScript
date: 2021-10-16 14:42:34
Javascript的痛点
且随着近几年前端领域的快速发展,让JavaScript迅速被普及和受广大开发者的喜爱,借助于JavaScript本身的
强大,也让使用JavaScript开发的人员越来越多。
优秀的JavaScript没有缺点吗?
其实上由于各种历史因素,JavaScript语言本身存在很多的缺点;
比如ES5以及之前的使用的var关键字关于作用域的问题;
比如最初JavaScript设计的数组类型并不是连续的内存空间;
比如直到今天JavaScript也没有加入类型检测这一机制;
JavaScript正在慢慢变好
不可否认的是,JavaScript正在慢慢变得越来越好,无论是从底层设计还是应用层面。
ES6、7、8等的推出,每次都会让这门语言更加现代、更加安全、更加方便。
但是知道今天,JavaScript在类型检测上依然是毫无进展
类型带来的问题
首先你需要知道,编程开发中我们有一个共识:错误出现的越早越好
能在写代码的时候发现错误,就不要在代码编译时再发现(IDE的优势就是在代码编写过程中帮助我们发现错误)。
能在代码编译期间发现错误,就不要在代码运行期间再发现(类型检测就可以很好的帮助我们做到这一点)。
能在开发阶段发现错误,就不要在测试期间发现错误,能在测试期间发现错误,就不要在上线后发现错误。
现在我们想探究的就是如何在 代码编译期间 发现代码的错误:
- JavaScript可以做到吗?不可以,我们来看下面这段经常可能出现的代码问题。
以下代码的问题:
- 没有对类型进行校验
- 没有对是否传入参数进行校验
function foo(message) {console.log(message.length)}foo('hello world')foo('你好啊')foo()// 渲染界面还有很多成千上万行js代码需要执行,去渲染界面
这是我们一个非常常见的错误:
这个错误很大的原因就是因为JavaScript没有对我们传入的参数进行任何的限制,只能等到运行期间才发现这个错误;
并且当这个错误产生时,会影响后续代码的继续执行,也就是整个项目都因为一个小小的错误而深入崩溃;
但是,如果我们可以给JavaScript加上很多限制,在开发中就可以很好的避免这样的问题了:
比如我们的getLength函数中str是一个必传的类型,没有调用者没有传编译期间就会报错;
比如我们要求它的必须是一个String类型,传入其他类型就直接报错;
那么就可以知道很多的错误问题在编译期间就被发现,而不是等到运行时再去发现和修改;
我们已经简单体会到没有类型检查带来的一些问题,JavaScript因为从设计之初就没有考虑类型的约束问题,所以造成了前端开发人员关于类型思维的缺失:
前端开发人员通常不关心变量或者参数是什么类型的,如果在必须确定类型时,我们往往需要使用各种判断验证;
从其他方向转到前端的人员,也会因为没有类型约束,而总是担心自己的代码不安全,不够健壮;
所以我们经常会说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不仅仅可以为我们的代码增加类型约束,而且可以培养我们前端程序员具备类型思维。
大前端的发展趋势
大前端是一群最能或者说最需要折腾的开发者:
客户端开发者:从Android到iOS,或者从iOS到Android,到RN,甚至现在越来越多的客户端开发者接触前端相关知识(Vue、React、Angular、小程序);
前端开发者:从jQuery到AngularJS,到三大框架并行:Vue、React、Angular,还有小程序,甚至现在也要接触客户端开发(比如RN、Flutter);
目前又面临着不仅仅学习ES的特性,还要学习TypeScript;
新框架的出现,我们又需要学习新框架的特性,比如vue3.x、react18等等;
但是每一样技术的出现都会让惊喜,因为他必然是解决了之前技术的某一个痛点的,而TypeScript真是解决了JavaScript存在的很多设计缺陷,尤其是关于类型检测的。
并且从开发者长远的角度来看,学习TypeScript有助于我们前端程序员培养 类型思维,这种思维方式对于完成大型项目尤为重要。
认识TypeScript
TypeScript是拥有类型的JavaScript超集,它可以编译成普通、干净、完整的JavaScript代码。
怎么理解上面的话呢?
我们可以将TypeScript理解成加强版的JavaScript。
JavaScript所拥有的特性,TypeScript全部都是支持的,并且它紧随ECMAScript的标准,所以ES6、ES7、ES8等新语法标准,它都是支持的;
并且在语言层面上,不仅仅增加了类型约束,而且包括一些语法的扩展,比如枚举类型(Enum)、元组类型(Tuple)等;
TypeScript在实现新特性的同时,总是保持和ES标准的同步甚至是领先;
并且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
正是因为有这些特性,TypeScript目前已经在很多地方被应用:
Angular源码在很早就使用TypeScript来进行了重写,并且开发Angular也需要掌握TypeScript;
Vue3源码也采用了TypeScript进行重写,在前面阅读源码时我们看到大量TypeScript的语法;
包括目前已经变成最流行的编辑器VSCode也是使用TypeScript来完成的;
包括在React中已经使用的ant-design的UI库,也大量使用TypeScript来编写;
目前公司非常流行Vue3+TypeScript、React+TypeScript的开发模式;
包括小程序开发,也是支持TypeScript的;
TypeScript的编译环境
在前面我们提到过,TypeScript最终会被编译成JavaScript来运行,所以我们需要搭建对应的环境:
- 我们需要在电脑上安装TypeScript,这样就可以通过TypeScript的Compiler将其编译成JavaScript;

- 所以,我们需要先可以先进行全局的安装:
- 安装命令
npm install typescript -g
- 查看版本
tsc --version

TypeScript的运行环境
如果我们每次为了查看TypeScript代码的运行效果,都通过经过两个步骤的话就太繁琐了:
第一步:通过tsc编译TypeScript到JavaScript代码;
第二步:在浏览器或者Node环境下运行JavaScript代码;
是否可以简化这样的步骤呢?
比如编写了TypeScript之后可以直接运行在浏览器上?
比如编写了TypeScript之后,直接通过node的命令来执行?
上面我提到的两种方式,可以通过两个解决方案来完成:
方式一:通过webpack,配置本地的TypeScript编译环境和开启一个本地服务,可以直接运行在浏览器上;(项目用这个)
方式二:通过ts-node库,为TypeScript的运行提供执行环境;(学习语法用这个)
首先方式二:
- 安装ts-node

- 然后还依赖另外两个包赖
tslib和@types/node两个包

- 现在就可以直接运行ts了
ts-node 03.ts
方式一:
- 首先新建一个文件夹。代表项目。然后在该文件夹目录下
npm init -y
- 在package.json文件里面,设置配置

- 然后下载ts-loader,和typescript.以及webpack ,webpack-cli

- 下载webpack-dev-server.

- 编辑webpack.config.js文件

变量的声明
我们已经强调过很多次,在TypeScript中定义变量需要指定 标识符 的类型。
所以完整的声明格式如下:
- 声明了类型后TypeScript就会进行类型检测,声明的类型可以称之为类型注解;
var/let/const 标识符: 数据类型 = 赋值;
比如我们声明一个message,完整的写法如下:
注意:这里的string是小写的,和String是有区别的
string是TypeScript中定义的字符串类型,String是ECMAScript中定义的一个类
// string: TypeScript的字符串类型// String: Javascript的字符串包装类型// 我们应该用小写const message:string="hello world"export {}
- 如果我们给message赋值其他类型的值,那么就会报错:
默认情况下,进行赋值的时候,会将赋值的值的类型,作为前面标识符的类型。这个过程称之为类型推导/推断。


Javascript和Typescript数据类型
我们经常说TypeScript的一个超级

1. JS类型-number类型
数字类型是我们开发中经常使用的类型,TypeScript和JavaScript一样,不区分整数类型(int)和浮点型(double),统一为number类型。
let num:number=123let num1:number=100let num2:number=0b100 //2进制 表示4let num3:number=0o100 //8进制 表示64let num4:number=0x100 //16进制 表示256console.log(num2,num3,num4);
2. JS类型-boolean类型
boolean类型只有两个取值:true和false,非常简单
let flag:boolean=trueflag=false
3. JS类型-string类型
string类型是字符串类型,可以使用单引号或者双引号表示,同时也支持ES6的模板字符串来拼接变量和字符串:
let message='hello world'const info=`my name is ${message}`console.log(info); //my name is hello world
4. JS类型-Array类型
// 确定一个事实,names是一个数组类型,但是数组中存放的是什么类型的元素// 一个数组中在TypeScript开发中,最好存放的是数据类型是固定的// 类型注解,type annotationconst names:Array<string>=['abc','ac','ba'] //不推荐const names2: string[]=['abc','ac','ba'] //推荐
5. JS类型-Object类型
object对象类型可以用于描述一个对象:

我们应该让他们自己推导

6. JS类型-null类型

7. JS类型-undefined类型

8. JS类型-Symbol类型
在ES5中,如果我们是不可以在对象中添加相同的属性名称的,比如下面的做法:

通常我们的做法是定义两个不同的属性名字:比如identity1和identity2。
但是我们也可以通过symbol来定义相同的名称,因为Symbol函数返回的是不同的值:

TypeScript类型-any类型
在某些情况下,我们确实无法确定一个变量的类型,并且可能它会发生一些变化,这个时候我们可以使用any类型(类似
于Dart语言中的dynamic类型)。
any类型有点像一种讨巧的TypeScript手段:
我们可以对any类型的变量进行任何的操作,包括获取不存在的属性、方法;
我们给一个any类型的变量赋值任何的值,比如数字、字符串的值;

如果对于某些情况的处理过于繁琐不希望添加规定的类型注解,或者在引入一些第三方库时,缺失了类型注解,这个时候我们可以使用any:
- 包括在Vue源码中,也会使用到any来进行某些类型的适配;
TypeScript类型-unknown类型
unknown是TypeScript中比较特殊的一种类型,它用于描述类型不确定的变量。
// unknown类型只能赋值给any和unknown类型// any类型可以赋值给任意类型let flag=truelet result: unknownif(flag){result=foo()}else{result=bar()}// unknown是防止拿到result去乱用,不能赋值给明确的类型console.log(result);
TypeScript类型-void类型
void通常用来指定一个函数是没有返回值的,那么它的返回值就是void类型:
- 我们可以将null和undefined赋值给void类型,也就是函数可以返回null或者undefined

TypeScript类型-never类型
never类型表示的是那些永不存在的值的类型。 例如, never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也可能是 never类型,当它们被永不为真的类型保护所约束时。
never类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never的子类型或可以赋值给never类型(除了never本身之外)。 即使 any也不可以赋值给never。
never 表示永远不会发生值的类型,比如一个函数:
如果一个函数中是一个死循环或者抛出一个异常,那么这个函数会返回东西吗?
不会,那么写void类型或者其他类型作为返回值类型都不合适,我们就可以使用never类型;

TypeScript类型-Tuple类型
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。 比如,你可以定义一对值分别为 string和number类型的元组。
数组
// 数组的弊端:我们没办法知道会拿到什么类型const info: any[]=['why',18,1.88]
元组
//元组的特点const info:[string,number,number]=['abc',18,1.88]const name=info[0]console.log(name.length);
// Declare a tuple typelet x: [string, number];// Initialize itx = ['hello', 10]; // OK// Initialize it incorrectlyx = [10, 'hello']; // Error
元组的应用场景

TypeScript类型-函数类型
在JavaScript开发中,函数是重要的组成部分,并且函数可以作为一等公民(可以作为参数,也可以作为返回值进行传递)。
那么在使用函数的过程中,函数是否也可以有自己的类型呢?
- 我们可以编写函数类型的表达式(Function Type Expressions),来表示函数类型;

在上面的语法中 (num1: number, num2: number) => void,代表的就是一个函数类型:
接收两个参数的函数:num1和num2,并且都是number类型;
并且这个函数是没有返回值的,所以是void;
在某些语言中,可能参数名称num1和num2是可以省略,但是TypeScript是不可以的:

剩余参数
从ES6开始,JavaScript也支持剩余参数,剩余参数语法允许我们将一个不定数量的参数放到一个数组中。

this
通常TypeScript会要求我们明确的指定this的类型:

函数重载
在TypeScript中,如果我们编写了一个add函数,希望可以对字符串和数字类型进行相加,应该如何编写呢?我们可能会这样来编写,但是其实是错误的:

在TypeScript中,我们可以去编写不同的重载签名(overload signatures)来表示函数可以以不同的方式进行调用;
一般是编写两个或者以上的重载签名,再去编写一个通用的函数以及实现;
比如我们对sum函数进行重构:
在我们调用sum的时候,它会根据我们传入的参数类型来决定执行函数体时,到底执行哪一个函数的重载签名;

假设我们现在有一个需求:定义一个函数,可以传入字符串或者数组,获取它们的长度。
这里有两种实现方案:
方案一:使用联合类型来实现;
方案二:实现函数重载来实现;

在开发中我们选择使用哪一种呢?在可能的情况下,尽量选择使用联合类型来实现;
TypeScript类型-枚举类型
枚举类型是为数不多的TypeScript特性有的特性之一:
枚举其实就是将一组可能出现的值,一个个列举出来,定义在一个类型中,这个类型就是枚举类型;
枚举允许开发者定义一组命名常量,常量可以是数字、字符串类型;

枚举类型默认是有值的,比如上面的枚举,默认值是这样的:
当然,我们也可以给枚举其他值:
这个时候会从100进行递增;
我们也可以给他们赋值其他的类型:

函数的参数类型
1. 普通函数
函数是JavaScript非常重要的组成部分,TypeScript允许我们指定函数的参数和返回值的类型。
- 参数的类型注解
声明函数时,可以在每个参数后添加类型注解,以声明函数接受的参数类型。
- 函数的返回值类型
我们也可以添加返回值的类型注解,这个注解出现在函数列表的后面。和变量的类型注解一样,我们通常情况下不需要返回类型注解,因为TypeScript会根据 return 返回值推断函数的返回类型

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

我们并没有指定item的类型,但是item是一个string类型:
这是因为TypeScript会根据forEach函数的类型以及数组的类型推断出item的类型;
这个过程称之为上下文类型(contextual typing),因为函数执行的上下文可以帮助确定参数和返回值的类型;
3. 对象类型
如果我们希望限定一个函数接受的参数是一个对象,这个时候要如何限定呢?
- 我们可以使用对象类型;

4. 可选类型
对象类型也可以指定哪些属性是可选的,可以在属性的后面添加一个?: 可选类型需要在必传参数的后面

5. 联合类型
TypeScript的类型系统允许我们使用多种运算符,从现有类型中构建新类型。
我们来使用第一种组合类型的方法:联合类型(Union Type)
联合类型是由两个或者多个其他类型组成的类型;
表示可以是这些类型中的任何一个值;
联合类型中的每一个类型被称之为联合成员(union’s members);

6. 字面量类型

需要结合联合类型才有意义

字面量推理


类型别名
当我们想要多次在其他地方使用时,就要编写多次。 比如我们可以给对象类型起一个别名:

传入给一个联合类型的值是非常简单的:只要保证是联合类型中的某一个类型的值即可
但是我们拿到这个值之后,我们应该如何使用它呢?因为它可能是任何一种类型。
比如我们拿到的值可能是string或者number,我们就不能对其调用string上的一些方法;
那么我们怎么处理这样的问题呢?
我们需要使用缩小(narrow)联合;
TypeScript可以根据我们缩小的代码结构,推断出更加具体的类型;

其中可选类型,可以看作是类型和undefined的联合类型
类型断言as
有时候TypeScript无法获取具体的类型信息,这个我们需要使用类型断言(Type Assertions)。
- 比如我们通过 document.getElementById,TypeScript只知道该函数会返回 HTMLElement ,但并不知道它具体的类型



非空类型断言
当我们编写下面的代码时,在执行ts的编译阶段会报错:
- 这是因为传入的message有可能是为undefined的,这个时候是不能执行方法的;


但是,我们确定传入的参数是有值的,这个时候我们可以使用非空类型断言:
p非空断言使用的是 ! ,表示可以确定某个标识符是有值的,跳过ts在编译阶段对它的检测;
可选链的使用
它是ES11(ES2020)中增加的特性:
可选链使用可选链操作符 ?.;
它的作用是当对象的属性不存在时,会短路,直接返回undefined,如果存在,那么才会继续执行;
虽然可选链操作是ECMAScript提出的特性,但是和TypeScript一起使用更般配;

??和!!的作用
有时候我们还会看到 !! 和 ?? 操作符,这些都是做什么的呢?
!!操作符:
将一个其他类型转换成boolean类型;类似于Boolean(变量)的方式;
??操作符:
它是ES11增加的新特性;**空值合并操作符**(**??)是一个逻辑操作符,当操作符的左侧是 null 或者 undefined 时,返回其 右侧操作数,否则返回左侧操作数;**
