邂逅TypeScript语法
1.JavaScript一门优秀的语言
◼
我始终相信:任何新技术的出现都是为了解决原有技术的某个痛点。
◼
JavaScript是一门优秀的编程语言吗?
每个人可能观点并不完全一致,但是从很多角度来看,JavaScript是一门
非常优秀的编程语言
;
而且,可以说在很长一段时间内
这个语言不会被代替
,并且会在
更多的领域
被大家广泛使用;
◼
著名的Atwood定律:
Stack Overflow的创立者之一的
Jeff Atwood
在2007年提出了著名的
Atwood定律
。
any application that can be written in JavaScript, will eventually be written in JavaScript.
任何可以使用JavaScript来实现的应用都最终都会使用JavaScript实现。
◼
其实我们已经看到了,这句话正在一步步被应验:
Web端
的开发我们一直都是使用JavaScript;
移动端
开发可以借助于ReactNative、Weex、Uniapp等框架实现跨平台开发;
小程序端
的开发也是离不开JavaScript;
桌面端
应用程序我们可以借助于Electron来开发;
服务器端
开发可以借助于Node环境使用JavaScript来开发。
2.JavaScript的痛点
◼
并且随着近几年前端领域的快速发展,让JavaScript迅速
被普及和受广大开发者的喜爱
,借助于
JavaScript本身的强大
,也让
使
用JavaScript开发的人员越来越多
。
◼
优秀的JavaScript没有缺点吗?
其实上由于各种历史因素,JavaScript语言本身
存在很多的缺点
;
比如ES5以及之前的
使用的var关键字关于作用域
的问题;
比如最初JavaScript设计的
数组类型并不是连续的内存空间
;
比如直到今天
JavaScript也没有加入类型检测
这一机制;
◼
JavaScript正在慢慢变好
不可否认的是,JavaScript正在慢慢变得越来越好,无论是从底层设计还是应用层面。
ES6、7、8等的推出,每次都会让这门语言
更加现代、更加安全、更加方便
。
但是知道今天,JavaScript在
类型检测上依然是毫无进展
(为什么类型检测如此重要,我后面会聊到)
3.类型带来的问题
◼
首先你需要知道,编程开发中我们有一个共识:
错误出现的越早越好
能在
写代码的时候
发现错误,就不要在
代码编译时
再发现(IDE的优势就是在代码编写过程中帮助我们发现错误)。
能在
代码编译期间
发现错误,就不要在
代码运行期间
再发现(类型检测就可以很好的帮助我们做到这一点)。
能在开发阶段发现错误,就不要在测试期间发现错误,能在测试期间发现错误,就不要在上线后发现错误。
◼
现在我们想探究的就是如何在
代码编译期间
发现代码的错误:
JavaScript可以做到吗?不可以,我们来看下面这段经常可能出现的代码问题。
4.类型错误
◼
这是我们一个非常常见的错误:
这个错误很大的原因就是因为JavaScript没有对我们
传入的参数进行任何的限制
,只能等到
运行期间才发现这个错误
;
并且当这个错误产生时,会影响后续代码的继续执行,也就是整个项目都因为
一个小小的错误而深入崩溃
;
◼
当然,你可能会想:我怎么可能犯这样低级的错误呢?
当我们写像我们上面这样的简单的demo时,这样的错误很容易避免,并且当出现错误时,也很容易检查出来;
但是当我们开发一个
大型项目
时呢?你能保证自己
一定不会出现这样的问题
吗?而且如果我们是调用别人的类库,又如何知
道让我们传入的到底是什么样的参数呢?
◼
但是,如果我们可以给
JavaScript加上很多限制
,在开发中就可以很好的
避免这样的问题
了:
比如我们的getLength函数中str是一个
必传的类型
,没有调用者没有传编译期间就会报错;
比如我们要求它的必须是一个
String类型
,传入其他类型就直接报错;
那么就可以知道很多的错误问题在
编译期间
就被发现,而不是等到运行时再去发现和修改;
5.类型思维的缺失
◼
我们已经简单体会到没有类型检查带来的一些问题,JavaScript因为从设计之初就没有考虑类型的约束问题,所以造成了前端开
发人员关于
类型思维的缺失
:
前端开发人员
通常不关心变量或者参数是什么类型的,如果在必须确定类型时,我们往往需要使用各种判断验证;
从其他方向转到前端的人员,也会因为没有类型约束,而总是担心自己的
代码不安全,不够健壮
;
◼
所以我们经常会说JavaScript
不适合开发大型项目
,因为当项目一旦庞大起来,这种宽松的类型约束会带来非常多的安全隐患,
多人员开发它们之间也
没有良好的类型契约
。
比如当我们去实现一个核心类库时,如果没有类型约束,那么需要对
别人传入的参数进行各种验证
来保证我们代码的健壮性;
比如我们去调用别人的函数,对方没有对函数进行任何的注释,我们只能去看里面的逻辑来理解这个函数需要
传入什么参数,
返回值是什么类型
;
6.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等也会是驾轻就熟;
7.认识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更加安全,而且给它带来了诸多好用的好用特性;
8.TypeScript的特点
◼
官方对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
9.TypeScript的编译环境
◼
在前面我们提到过,TypeScript最终会被编译成JavaScript来运行,所以我们需要搭建对应的环境:
我们
需要在电脑上安装TypeScript
,这样就可以
通过TypeScript的Compiler将其编译成JavaScript
;
◼
所以,我们需要先可以先进行全局的安装:
#
安装命令
npm install typescript -g
#
查看版本
tsc --version
10.TypeScript的运行环境
◼
如果我们每次为了查看TypeScript代码的运行效果,都通过经过两个步骤的话就太繁琐了:
第一步:通过
tsc编译TypeScript到JavaScript代码
;
第二步:在
浏览器或者Node环境下运行JavaScript代码
;
◼
是否可以简化这样的步骤呢?
比如
编写了TypeScript之后可以直接运行在浏览器上
?
比如
编写了TypeScript之后,直接通过node的命令来执行
?
◼
上面我提到的两种方式,可以通过两个解决方案来完成:
方式一:通过
webpack,配置本地的TypeScript编译环境和开启一个本地服务,可以直接运行在浏览器
上;
方式二:通过
ts-node库,为TypeScript的运行提供执行环境
;
◼
方式一:webpack配置
方式一在之前的TypeScript文章有写过,如果需要可以自行查看对应的文章;
https://mp.weixin.qq.com/s/wnL1l-ERjTDykWM76l4Ajw
;
11.使用ts-node
◼
方式二:安装ts-node
◼
另外ts-node需要依赖 tslib 和 @types/node 两个包:
◼
现在,我们可以直接通过 ts-node 来运行TypeScript的代码:
npm install ts-node -g
npm install tslib @types/node -g
ts-node math.ts
12.变量的声明
◼
我们已经强调过很多次,在TypeScript中定义变量需要指定 标识符 的类型。
◼
所以完整的声明格式如下:
声明了类型后TypeScript就会进行
类型检测
,声明的类型可以称之为
类型注解(Type Annotation)
;
var/let/const
标识符
:
数据类型
=
赋值
;
◼
比如
我们声明一个message,完整的写法如下:
注意:这里的string是小写的,和String是有区别的
string是TypeScript中定义的字符串类型,String是ECMAScript中定义的一个类
◼
如果我们给message赋值其他类型的值,那么就会报错:
13.声明变量的关键字
◼
在TypeScript定义变量(标识符)和ES6之后一致,可以使用var、let、const来定义
◼
当然,在tslint中并不推荐使用var来声明变量:
可见,在TypeScript中并不建议再使用var关键字了,主要原因和ES6升级后let和var的区别是一样的,var是没有块级作用域
的,会引起很多的问题,这里不再展开探讨。
13.变量的类型推导(推断)
◼
在开发中,有时候为了方便起见我们并不会在声明每一个变量时都写上对应的数据类型,我们更希望可以通过TypeScript本身的
特性帮助我们推断出对应的变量类型:
◼
如果我们给message赋值“Hello world”:
◼
这是因为在一个变量第一次赋值时,会根据后面的赋值内容的类型,来推断出变量的类型:
上面的message就是因为后面赋值的是一个布尔类型,所以message虽然没有明确的说明,但是依然是一个布尔类型;
JavaScript和TypeScript的数据类型
1.JavaScript类型 – number类型
◼
数字类型是我们开发中经常使用的类型,TypeScript和JavaScript一样,不区分整数类型(int)和浮点型(double),统一为
number类型。
let num = 12
num = 132
num = 128
//学习过ES6应该知道,ES6新增了二进制和八进制的表示方法,而TypeScript也是支持二进制、八进制、十六进制的表示:
num = 100
num = 0b110
num = 0o555
num = 0xf23
2.JavaScript类型 – boolean类型
◼
boolean类型只有两个取值:true和false,非常简单
let bl: boolean = false;
bl = true;
bl = 20 > 33;
3.JavaScript类型 – string类型
◼
string类型是字符串类型,可以使用单引号或者双引号表示:
◼
同时也支持ES6的模板字符串来拼接变量和字符串:
4.JavaScript类型 – Array类型
◼
数组类型
的定义也非常简单,有两种方式:
Array<string>事实上是一种泛型的写法,我们会在后续中学习它的用法;
const arryone:string[] = [ 'abc', 'bac', 'cba']
const arrytwo: Array<string> = ['abc', 'bac', 'cba']
◼
如果添加其他类型到数组中,那么会报错
5.JavaScript类型 – Object类型
◼
object对象类型可以用于描述一个对象:
◼
但是从myinfo中我们不能获取数据,也不能设置数据:
// 1.直接将类型定义为object类型,则无法访问其属性。
let obj: object = {
name: "cui",
age: 18,
};
console.log(obj.name); //类型“object”上不存在属性“name”
//2.正确写法
let objone: {
name: string;
age: number;
} = {
name: "cui",
age: 18,
};
console.log(objone.name); //cui
6.JavaScript类型 – Symbol类型
◼
在ES5中,如果我们是不可以在对象中添加相同的属性名称的,比如下面的做法:
◼
通常我们的做法是定义两个不同的属性名字:比如
identity1
和
identity2
。
◼
但是我们也可以通过
symbol
来定义相同的名称,因为
Symbol
函数返回的是不同的值:
7.JavaScript类型 – null和undefined类型
◼
在 JavaScript 中,undefined 和 null 是两个基本数据类型。
◼
在TypeScript中,它们各自的类型也是undefined和null,也就意味着它们既是实际的值,也是自己的类型:
8.函数的参数类型
◼
函数是JavaScript非常重要的组成部分,TypeScript允许我们指定函数的参数和返回值的类型。
◼
参数的类型注解
声明函数时,可以在
每个参数后添加类型注解
,以
声明函数接受的参数类型
:
//1.不设置参数类型,则参数类型为any,编译时不会报错,运行时会报错
// function sum(num1, num2) {
// return num1 + num2;
// }
// console.log(sum({}, {}));
//2.定义一个ts函数必须指定参数类型。
function sum(num1: number, num2: number) {
return num1 + num2;
}
const res = sum(1,2) //res类型为number(类型推论)
console.log(sum(1, 2)); // 12
export { }
9.函数的返回值类型
◼
我们也可以添加返回值的类型注解,这个注解出现在函数列表的后面
//返回值可以明确指定,也可以 TypeScript 会自动推断出返回值类型
function sum(num1: number, num2: number): number {
return num1 + num2;
}
const res = sum(1, 2); //res类型为number(类型推论)
console.log(sum(1, 2)); // 12
◼
和变量的类型注解一样,我们通常情况下不需要返回类型注解,因为TypeScript会根据 return 返回值推断函数的返回类型:
某些第三方库处于方便理解,会明确指定返回类型,看个人喜好;
11.匿名函数的参数
◼
匿名函数与函数声明会有一些不同:
当一个函数出现在TypeScript可以确定该函数会被如何调用的地方时;
该函数的参数
会自动指定类型;
// 匿名函数的参数类型 不需要指定类型,编译器会自动推导出类型 这个过程叫上下文类型
const names = ["Alice", "Bob", "Charlie"];
names.forEach((item) => console.log(item.toUpperCase()));
export { }
◼ 我们并没有指定item的类型,但是item是一个string类型:
这是因为TypeScript会根据forEach函数的类型以及数组的
类型推断出item的类型
;
这个过程称之为
上下文类型(
contextual typing
)
,因为函数执行的上下文可以帮助确定参数和返回值的类型;
12.对象类型
◼
如果我们希望限定一个函数接受的参数是一个对象,这个时候要如何限定呢?
我们可以使用对象类型;
//对象类型跟函数类型联合使用
//可选类型,在属性后面加一个?
type PointType = { x: number; y?: number }
function printCoordinate(point: PointType) {
console.log("x:", point.x);
console.log("y:", point.y);
}
printCoordinate({ x: 12, y: 12 });
◼ 在这里我们使用了一个对象来作为类型:
在对象我们可以添加属性,并且告知TypeScript该属性需要是什么类型;
属性之间可以使用 , 或者 ; 来分割,最后一个分隔符是可选的;
每个属性的类型部分也是可选的,如果不指定,那么就是any类型;
13.可选类型
◼ 对象类型也可以指定哪些属性是可选的,可以在属性的后面添加一个?:
14.TypeScript类型 - any类型
◼
在某些情况下,我们确实
无法确定一个变量的类型
,
并且可能它会发生一些变化
,这个时候我们可以使用
any类型
(类似于Dart
语言中的dynamic类型)。
◼
any类型有点像一种讨巧的TypeScript手段:
我们可以
对any类型的变量进行任何的操作
,包括
获取不存在的属性、方法
;
我们
给一个any类型的变量赋值任何的值,比如数字、字符串的值
;
◼
如果对于某些情况的处理过于繁琐不希望添加规定的类型注解,或者在引入一些第三方库时,缺失了类型注解,这个时候我们可
以使用any:
包括
在Vue源码中,也会使用到any来进行某些类型的适配
;
15.TypeScript类型 - unknown类型
◼
unknown是TypeScript中比较特殊的一种类型,它用于描述类型不确定的变量。
和any类型有点类似,但是unknown类型的值上做任何事情都是不合法的;
◼
什么意思呢?我们来看下面的场景
//1.unknown 表示不能对标识符进行任何操作
let foo: unknown = "aaa";
// foo = 123;
console.log(foo.length)//“foo”的类型为“未知”,不能访问“length”属性
// 2.unknown类型缩小后可以进行操作
if (typeof foo === "string") {
console.log(foo.length);
}
16.TypeScript类型 - void类型
◼
void通常用来指定一个函数是没有返回值的,那么它的返回值就是void类型:
◼
这个函数我们没有写任何类型,那么它默认返回值的类型就是void的,我们也可以显示的来指定返回值是void:
◼
这里还有一个注意事项:
我们可以将undefined赋值给void类型,也就是函数可以返回undefined
◼
当基于上下文的类型推导(Contextual Typing)推导出返回类型为 void 的时候,并不会强制函数一定不能返回内容。
// 1.如果在ts当中一个函数没有返回值那么它的返回值类型就是void。
// 2.如果一个函数的返回值类型是void那么也可以返回undefined
// 3.当函数类型推论为void时,不会强制函数一定不能返回内容
function sum(num1: number, num2: number) {
console.log(num1 + num2);
}
function sum1(num1: number, num2: number): void {
console.log(num1 + num2);
}
function sum3(num1: number, num2: number): void {
console.log(num1 + num2);
return undefined; // 正确
}
// 函数类型
type FooType = (...args: any[]) => void;
function delay(fn: FooType) {
setTimeout(() => {
fn("cui", 18);
}, 1000);
}
delay((name,age) => {
console.log(`name: ${name}, age: ${age}`);
})
17.TypeScript类型 - never类型
◼
never 表示永远不会发生值的类型,比如一个函数:
如果一个函数中是一个死循环或者抛出一个异常,那么这个函数会返回东西吗?
不会,那么写void类型或者其他类型作为返回值类型都不合适,我们就可以使用never类型
// 1.一个函数是一个死循环
//2.函数抛出一个异常
//3.在条件语句中,如果 TypeScript 能够推断出某个分支永远不会执行,那么该分支的类型会被推断为 never 。
function foo() {
while (true) {
console.log("--------");
}
}
foo(); //void
const inFin = () => {
while (true) {
console.log("Hello, world!");
}
};
inFin(); //never
//
function foo1() {
return [];
}
const fun = (message: string) => {
throw new Error(message);
};
// 在条件语句中,如果 TypeScript 能够推断出某个分支永远不会执行,那么该分支的类型会被推断为 never 。
//用处:封转框架/工具库时可以使用never
type Value = string | number;
const processValue = (Value: Value) => {
if (typeof Value === "string") {
console.log(`The value is a string: ${Value}`);
} else if (typeof Value === "number") {
console.log(`The value is a number: ${Value}`);
} else {
const n: never = Value;
}
};
processValue("string");
processValue(123);
const res = processValue(true);
export {};
18.TypeScript类型 - tuple类型
◼ tuple是元组类型,很多语言中也有这种数据类型,比如Python、Swift等。
//保存个人信息 cui 18 1.88
// 定义tuple类型
// 1.使用数组不合适,数组当中最好放想同的数据类型,获取值后不能明确的知道数据类型
const info: any[] = ["cui", 18, 1.88];
const value = info[0]; // any类型
const age = info[1]; // any类型
const height = info[2]; // any类型
// 2.使用元组类型
const info2: [string, number, number] = ["cui", 18, 1.88];
const value2 = info2[0]; // string类型
const age2 = info2[1]; // number类型
const height2 = info2[2]; // number类型
//3.使用对象类型
const info3: { name: string; age: number; height: number } = {
name: "cui",
age: 18,
height: 1.88,
};
const value3 = info3["name"]; // string
export { }
◼
那么tuple和数组有什么区别呢?
首先,
数组中通常建议存放相同类型的元素
,
不同类型的元素是不推荐放在数组中
。(可以放在对象或者元组中)
其次,
元组中每个元素都有自己特性的类型,根据索引值获取到的值可以确定对应的类型
;
TypeScript语法细节
1.联合类型
◼
TypeScript的类型系统允许我们使用多种运算符,从
现有类型中构建新类型
。
◼
我们来使用第一种组合类型的方法:
联合类型(Union Type)
联合类型是
由两个或者多个其他类型组成的类型
;
表示
可以是这些类型中的任何一个值
;
联合类型中的每一个类型被称之为
联合成员(union's
members
)
;
◼
传入给一个联合类型的值是非常简单的:只要保证是联合类型中的某一个类型的值即可
但是我们拿到这个值之后,我们应该如何使用它呢?因为
它可能是任何一种类型
。
比如我们拿到的值可能是string或者number,我们就不能对其调用string上的一些方法;
◼
那么我们怎么处理这样的问题呢?
我们需要使用
缩小(narrow)联合
(后续我们还会专门讲解缩小相关的功能);
TypeScript可以
根据我们缩小的代码结构
,
推断出更加具体的类型
;
function Printf(id: number | string) {
console.log(id);
//类型缩小
if (typeof id === "string") {
console.log(id.length);
} else {
console.log(id);
}
}
2.类型别名
◼
在前面,我们通过在类型注解中编写 对象类型 和 联合类型,但是当我们想要多次在其他地方使用时,就要编写多次。
◼
比如我们可以给对象类型起一个别名:
// function printID(id: number | string) {
// console.log(id)
// }
//类型别名 type
type ID = number | string;
function printID2(id: ID) {
console.log(id)
}
printID2(123)
printID2("cui");
3.接口的声明
◼
在前面我们通过type可以用来声明一个对象类型:
//类型别名 type
type Point0 = {
x: number;
y: number;
}
◼
在前面我们通过type可以用来声明一个对象类型:
interface Point0 {
x: number;
y: number;
}
◼ 那么它们有什么区别呢?
类型别名和接口非常相似,在定义对象类型时,大部分时候,你可以任意选择使用。
接口的几乎所有特性都可以在 type 中使用(后续我们还会学习interface的很多特性);
interface和type区别
◼
我们会发现interface和type都可以用来定义对象类型,那么在开发中定义对象类型时,到底选择哪一个呢?
如果是
定义非对象类型
,通常
推荐使用type
,比如Direction、Alignment、一些Function;
◼
如果是定义对象类型,那么他们是有区别的:
interface 可以重复的对某个接口来定义属性和方法;
而type定义的是别名,别名是不能重复的;
interface Point {
x: number;
y: number;
}
interface Point {
z?: number;
}
◼
所以,interface可以为现有的接口提供更多的扩展。
接口
还有很多其他的用法
,我们会在后续详细学习
4.交叉类型
◼
前面我们学习了联合类型:
联合类型表示多个类型中一个即可
type typeString = "up" | "down" | "left" | "right";
◼ 还有另外一种类型合并,就是交叉类型(Intersection Types):
交叉类似表示需要满足多个类型的条件;
交叉类型使用 & 符号
;
◼
我们来看下面的交叉类型:
表达的含义是
number和string要同时满足
;
但是
有同时满足是一个number又是一个string的值
吗?其实是没有的,所以MyType其实是一个never类型;
//交叉类型
type Point = number & string
交叉类型的应用
◼
所以,在开发中,我们进行交叉时,通常是对对象类型进行交叉的:
type NewType = number & string; //没有任何实际意义
interface Ikun {
name: string;
age: number;
}
interface Icoder {
name: string;
coding: () => void;
}
// 1. 交叉类型 & 需要满足多个条件的内容
const info: Ikun & Icoder = {
name: "cui",
age: 18,
coding: () => {
console.log("coding...");
},
};
5.类型断言as
◼
有时候TypeScript无法获取具体的类型信息,这个我们需要使用类型断言(Type Assertions)。
比如我们通过 document.getElementById,TypeScript只知道该函数会返回 HTMLElement ,但并不知道它具体的类型:
//1.有时候ts不能获取具体的类型信息,这时候就可以使用类型断言来告诉ts我们确切的知道变量的类型。
//2.ts断言只能断言更加具体的类型,或者不太具体的类型 此规则可以防止不可能的强制转换
const div = document.querySelector('div') as HTMLDivElement // 类型断言
div.innerText = 'Hello TypeScript!' // 正确的使用 TypeScript 类型
// const div1 = div as string 错误处理
//ts类型检测正确,但是代码本身不太正确
const div2 = div as any
◼
TypeScript只允许类型断言转换为 更具体 或者 不太具体 的类型版本,此规则可防止不可能的强制转换:
6.非空类型断言!
◼
当我们编写下面的代码时,在执行ts的编译阶段会报错:
这是因为传入的message有可能是为undefined的,这个时候是不能执行方法的;
◼
但是,我们确定传入的参数是有值的,这个时候我们可以使用非空类型断言:
非空断言使用的是 !
,表示
可以确定某个标识符是有值
的,
跳过ts在编译阶段对它的检测
;
interface IPerson {
name: string;
age: number;
friend?: {
name: string;
};
}
const info: IPerson = {
name: "cui",
age: 18,
};
// 1. 接口中的可选属性 可选链?
console.log(info.friend?.name);
//2.类型缩小
if (info.friend) {
console.log(info.friend.name);
}
//3.非空类型断言 !告诉TypeScript,我知道这个属性肯定不是null或undefined
console.log(info.friend!.name)
export { }
7.字面量类型
◼
除了前面我们所讲过的类型之外,也可以使用字面量类型(literal types):
// 1.字面量类型的基本使用
const name1: "cui" = "cui";
let age: 18 = 18;
◼ 那么这样做有什么意义呢?
默认情况下这么做是没有太大的意义的,但是我们可以将多个类型联合在一起;
//2.将多个字面量类型合并为一个类型
type Direction = "up" | "down" | "left" | "right";
const d1: Direction = "up";
字面量推理
◼ 我们来看下面的代码:
◼
这是因为我们的对象在进行字面量推理的时候,info其实是一个 {url: string, method: string},所以我们没办法将一个 string
赋值给一个 字面量 类型。
8.类型缩小
◼
什么是类型缩小呢?
类型缩小的英文是
Type Narrowing
(也有人翻译成类型收窄);
我们可以通过类似于
typeof padding === "number"
的判断语句,来
改变TypeScript的执行路径
;
在给定的执行路径中,我们可以
缩小比声明时更小的类型
,这个过程称之为
缩小( Narrowing )
;
而我们编写的
typeof padding === "number
可以称之为
类型保护(type guards)
;
◼
常见的类型保护有如下几种:
typeof
平等缩小(比如===、!==)
instanceof
in
等等...
9.1typeof
◼ 在 TypeScript 中,检查返回的值typeof是一种类型保护:
因为 TypeScript 对如何typeof操作不同的值进行编码。
9.2平等缩小
◼ 我们可以使用Switch或者相等的一些运算符来表达相等性(比如===, !==, ==, and != ):
9.3 instanceof
◼
JavaScript 有一个运算符来检查一个值是否是另一个值的“实例”:
9.4in操作符
◼
Javascript 有一个运算符,用于确定对象是否具有带名称的属性:in运算符
如果指定的属性在指定的对象或其原型链中,则
in 运算符
返回true;
9.TypeScript函数类型
◼
在JavaScript开发中,函数是重要的组成部分,并且函数可以
作为一等公民
(可以作为参数,也可以作为返回值进行传递)。
◼
那么在使用函数的过程中,函数是否也可以有自己的类型呢?
◼
我们可以编写
函数类型的表达式(Function Type Expressions),来表示函数类型
;
9.1TypeScript函数类型解析
◼
在上面的语法中 (num1: number, num2: number) => void,代表的就是一个函数类型:
接收两个参数的函数:num1和num2,并且都是number类型;
并且这个函数是没有返回值的,所以是void;
◼
注意:在
某些语言中,可能参数名称num1和num2是可以省略,但是TypeScript是不可以的
10.调用签名(Call Signatures)
◼
在
JavaScript
中,函数除了可以被调用,自己也是可以有属性值的。
然而前面讲到的函数类型表达式
并不能支持声明属性
;
如果我们想描述一个带有属性的函数,我们可以在一个对象类型中写一个
调用签名(
call signature
)
;
◼
注意这个语法跟函数类型表达式稍有不同,在参数列表和返回的类型之间用的是 : 而不是 =>。
11.构造签名 (Construct Signatures)
◼
JavaScript 函数也可以使用 new 操作符调用,当被调用的时候,TypeScript 会认为这是一个构造函数(constructors),因为
他们会产生一个新对象。
你可以写一个
构造签名( Construct Signatures ),
方法是在调用签名前面加一个 new 关键词;
12.参数的可选类型
◼ 我们可以指定某个参数是可选的:
◼ 这个时候这个参数y依然是有类型的,它是什么类型呢? number | undefined
◼ 另外可选类型需要在必传参数的后面:
13. 默认参数
◼
从ES6开始,JavaScript是支持默认参数的,TypeScript也是支持默认参数的:
◼
这个时候y的类型其实是 undefined 和 number 类型的联合。
14.剩余参数
◼
从ES6开始,JavaScript也支持剩余参数,剩余参数语法允许我们将一个不定数量的参数放到一个数组中。
15.函数的重载(了解)
◼
在TypeScript中,如果我们编写了一个add函数,希望可以对字符串和数字类型进行相加,应该如何编写呢?
◼
我们可能会这样来编写,但是其实是错误的:
◼
那么这个代码应该如何去编写呢?
在TypeScript中,我们可以去
编写不同的重载签名(
overload signatures
)
来表示函数可以
以不同的方式进行调用
;
一般是
编写两个或者以上的重载签名
,再去
编写一个通用的函数以及实现
;
sum函数的重载
◼
比如我们对sum函数进行重构:
在我们调用sum的时候,它会
根据我们传入的参数类型来决定执行函数体
时,到底
执行哪一个函数的重载签名
;
◼
但是注意,有实现体的函数,是不能直接被调用的:
16.联合类型和重载
◼
我们现在有一个需求:定义一个函数,可以传入字符串或者数组,获取它们的长度。
◼
这里有两种实现方案:
方案一:使用联合类型来实现;
方案二:实现函数重载来实现;
◼
在开发中我们选择使用哪一种呢?
在可能的情况下,尽量选择使用联合类型来实现;
17.可推导的this类型
◼
this是JavaScript中一个比较难以理解和把握的知识点:
coderwhy公众号也有一篇文章专门讲解this:
https://mp.weixin.qq.com/s/hYm0JgBI25grNG_2sCRlTA
;
◼
当然在目前的Vue3和React开发中你不一定会使用到this:
Vue3的Composition API中很少见到this,React的Hooks开发中也很少见到this了;
◼
但是我们还是简单掌握一些TypeScript中的this,TypeScript是如何处理this呢?我们先来看两个例子:
◼
上面的代码默认情况下是可以正常运行的,也就是TypeScript在编译时,认为我们的this是可以正确去使用的:
这是因为在没有指定this的情况,this默认情况下是any类型的;
17.1.this的编译选项
◼
VSCode在检测我们的TypeScript代码时,默认情况下运行不确定的this按照any类型去使用。
但是我们可以创建一个tsconfig.json文件,并且在其中告知VSCodethis必须明确执行(不能是隐式的);
◼
在设置了
noImplicitThis
为true时, TypeScript会根据上下文推导this,但是在不能正确推导时,就会报错,需要我们明确
的指定this。
17.2指定this的类型
◼
在开启noImplicitThis的情况下,我们必须指定this的类型。
◼
如何指定呢?函数的第一个参数类型:
函数的第一个参数我们可以根据该函数之后被调用的情况,用于声明this的类型(名词必须叫this);
在后续调用函数传入参数时,从第二个参数开始传递的,this参数会在编译后被抹除;
17.3this相关的内置工具
◼
Typescript
提供了一些工具类型来辅助进行常见的类型转换,这些类型全局可用。
◼
ThisParameterType
:
用于提取一个函数类型Type的this (opens new window)参数类型;
如果这个函数类型没有this参数返回unknown;
◼
OmitThisParameter
:
用于移除一个函数类型Type的this参数类型, 并且返回当前的函数类型
17.4this相关的内置工具 - ThisType
◼
这个类型不返回一个转换过的类型,它被用作标记一个上下文的this类型。(官方文档)
事实上官方文档的不管是解释,还是案例都没有说明出来ThisType类型的作用;
◼
我这里用另外一个例子来给大家进行说明: