一、TS类型注解
一种为变量添加类型约束的形式,(什么类型的变量,赋值什么类型的数据,否则会报错)
语法类型:var/let/const 变量名:数据类型=值
number和Number的区别?
虽说在声明的时候,并不会进行报错,但是在设置数据类型注解的时候,尽量使用小写,大部分基本数据类型都是小写的,Number一般代表的是一个类
ts中的变量的声明,注意变量名是否重复
在ts中使用let声明的变量,会在全局的文件中进行查找,如果有重复的,该变量的声明就会报错(有波浪线),所以在ts文件中底部
export {} 形成一个块级作用域
什么时候进行类型注解?
默认情况下 ts会帮我们将初始赋值的数据类型作为当前变量的类型
当只定义一个变量,没有初始值的时候,给变量进行类型注解,避免重复赋值不同类型的数据
let k;
k=1
k="1"
//应为
let k:string
k="12"
类型注解主要有number、boolean、string、Array、Object、Symbol、null和undefined、any、unknown、void、never、tuple
1.1变量的声明
在TypeScript中定义变量需要指定 标识符 的类型,所以完整的声明格式如下:
声明了类型后TypeScript就会进行类型检测,声明的类型可以称之为类型注解;
var/let/const 标识符: 数据类型 = 赋值;
比如我们声明一个message,完整的写法如下:
- 注意:这里的string是小写的,和String是有区别的
- string是TypeScript中定义的字符串类型,String是ECMAScript中定义的一个类
let message: string = "Hello World";
message = "Hello TypeScript"; // 正确的做法
message = 20; // 错误的做法,因为message是一个string类型
1.2声明变量的关键字
在TypeScript定义变量(标识符)和ES6之后一致,可以使用var、let、const来定义。
当然,在tslint中并不推荐使用var来声明变量:
可见,在TypeScript中并不建议再使用var关键字了,主要原因和ES6升级后let和var的区别是一样的,var是没有块级作用域的。
var myname: string = "abc";
let myage: number = 20;
const myheight: number = 1.88;
我们会发现使用var关键字会有一个警告:
1.3 变量的类型推断
在开发中,有时候为了方便起见我们并不会在声明每一个变量时都写上对应的数据类型,我们更希望可以通过TypeScript本身的特性帮助我们推断出对应的变量类型:
let message = "Hello World";
上面的代码我们并没有指定类型,但是message实际上依然是一个字符串类型:
这是因为在一个变量第一次赋值时,会根据后面的赋值内容的类型,来推断出变量的类型:
- 上面的message就是因为后面赋值的是一个string类型,所以message虽然没有明确的说明,但是依然是一个string类型
let message = "Hello World"; // string类型
let age = 20; // number类型
let isFlag = true; // boolean类型
1.4 JS和TS的数据类型
我们经常说TypeScript是JavaScript的一个超集:
number类型
数字类型是我们开发中经常使用的类型,TypeScript和JavaScript一样,不区分整数类型(int)和浮点型(double),统一为number类型。
// 1.数字类型基本定义
let num = 100;
num = 20;
num = 6.66;
如果你学习过ES6应该知道,ES6新增了二进制和八进制的表示方法,而TypeScript也是支持二进制、八进制、十六进制的表示:
// 2.其他进制表示
num = 100; // 十进制
num = 0b110; // 二进制
num = 0o555; // 八进制
num = 0xf23; // 十六进制
boolean类型
boolean类型只有两个取值:true和false,非常简单
// boolean类型的表示
let flag: boolean = true;
flag = false;
flag = 20 > 30;
string类型
string类型是字符串类型,可以使用单引号或者双引号表示:
// string类型表示
let message: string = "Hello World";
message = 'Hello TypeScript';
同时也支持ES6的模板字符串来拼接变量和字符串:
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);
Array类型
数组类型的定义也非常简单,有两种方式:
const names1: string[] = ["why", "abc", "cba"];
const names2: Array<string> = ["why", "abc", "cba"];
如果添加其他类型到数组中,那么会报错。
Object类型
object对象类型可以用于描述一个对象:
// object类型表示
const myinfo: object = {
name: "why",
age: 20,
height: 1.88,
};
但是上面的代码会报一个警告:
这是因为TSLint建议我们所有的key按照字母进行排序,但是这个并不是特别有必要,我们还是可以关闭掉:
"object-literal-sort-keys": false
从myinfo中我们不能获取数据,也不能设置数据,因为属性是不可以访问的,如果我们访问myinfo中的属性,会发现报错。
这是因为TypeScript并不知道某一个object类型上面就有一个name的属性。
但是如果我们让它是类型推断的,就可以正常的访问:
这是因为推导出来的类型,是如下的类型
Symbol类型
在ES5中,如果我们希望不可以在对象中添加相同的属性名称,可以使用下面的做法:
const person = {
identity: "程序员",
identity: "会计师",
}
通常我们的做法是定义两个不同的属性名字:比如identity1和identity2。
但是我们也可以通过symbol来定义相同的名称,因为Symbol函数返回的是不同的值:
const s1 = Symbol("identity");
const s2 = Symbol("identity");
const person = {
[s1]: "程序员",
[s2]: "会计师",
};
null和undefined类型
在 JavaScript 中,undefined 和 null 是两个基本数据类型。
在TypeScript中,它们各自的类型也是undefined和null,也就意味着它们既是实际的值,也是自己的类型:
const n: null = null;
const u: undefined = undefined;
TS类型-any
在某些情况下,我们确实无法确定一个变量的类型,并且可能它会发生一些变化,这个时候我们可以使用any类型(类似于Dart语言中的dynamic类型)。
any类型有点像一种讨巧的TypeScript手段:
- 我们可以对any类型的变量进行任何的操作,包括获取不存在的属性、方法;
- 我们给一个any类型的变量赋值任何的值,比如数字、字符串的值;
let a: any = "why";
a = 123;
a = true;
const aArray: any[] = ["why", 18, 1.88];
如果对于某些情况的处理过于繁琐不希望添加规定的类型注解,或者在引入一些第三方库时,缺失了类型注解,这个时候我们可以使用any:
包括在Vue源码中,也会使用到any来进行某些类型的适配;
TS类型-unknown
unknown是TypeScript中比较特殊的一种类型,它用于描述类型不确定的变量,可以把任何值赋值给 unknown
let value:any;
value = ture;
value = 1;
但是不能调用属性和方法
value.length; // 错误写法
如果需要调用属性和方法,那么你可能需要类型断言
let value:unknown;
value = 'hello';
(value as string).length
再或者使用类型保护
let value:unknown;
value = 'hello';
if (typeof value === 'string') {
value.length
}
联合类型中的 unknown定义
如果联合类型中有unknown,那么最终得到的都是unknown类型
type U1 = unknown | null;
type U2 = unknown | string;
type U3 = unknown | number;
类型别名 U1,U2,U3 都是 unknown 类型
TS类型-void
void通常用来指定一个函数是没有返回值的,那么它的返回值就是void类型:
const sum = (num1: number, num2: number) => {
return num1 + num2;
};
// 相当于下面的写法
const sum: (num1: number, num2: number) => number = (num1: number, num2: number) => {
return num1 + num2;
};
这个函数我们没有写任何类型,那么它默认返回值的类型就是void的,我们也可以显示的来指定返回值是void:
我们可以将null和undefined赋值给void类型,也就是函数可以返回null或者undefined
const sayHello: (name: string) => void = (name: string) => {
console.log("hello " + name);
};
TS类型-never
never 表示永远不会有值的类型,比如一个函数:
如果一个函数中是一个死循环或者抛出一个异常,那么这个函数会返回东西吗?
不会,那么写void类型或者其他类型作为返回值类型都不合适,我们就可以使用never类型;
如果我现在调用handleMessage(true)传了一个布尔类型的参数,那么肯定会有错误提示,然后我们会发现message并没有boolean的参数类型注解,添加之后我们此时可能会忘记在switch…case的结构体中添加对应的判断条件,编译的时候肯定会报错,我们又得根据提示信息来添加对应判断处理传递进来的布尔类型,此时如果使用never数据类型,明确message不会有值,那么如果我们忘记在switch…case中添加判断布尔类型的代码时,肯定会走到default分支,那么就会对check进行赋值,此时就会有错误提示,我们就能马上知道哪里有问题,不需要等到ts代码编译运行了才知道错误,在复杂的大型项目中还是能感受出来的,例如Vue3。
与 void 的区别,通常情况下我们会这样定义无返回值的函数:
fun: () => void;
但在 TS 中 void 至少包含了以下几个子类型:undefined、null
例如下面的实例:
// strictNullChecks = false 时
const v: void // 等同于 undefined | null
// strictNullChecks = true 时
const v: void // 等同于 undefined
完全无返回值。而 never 是完全没有返回值的类型,只有一种情况会如此:代码阻断。
那么当执行 throw new Error 、 return process.exit(1)、while(true){} 时都满足此条件:
function error(message: string): never {
throw new Error(message);
}
如果函数的返回值类型是 never 意味的此函数必须不能被顺利完整执行,而发生中断行为。
所有类型的子类型
never 是所有类型的子类型,因此可以理解为:所有的函数的返回值都包含 never 类型:
function fun(s: string): number {
if (s == 'a') return 1;
if (s == 'b') return 2;
throw new Error;
}
// 等同于 fun(s: string): number | never
只要不是单独申明为 never 类型,都会被显性的展示起来,因此下面的声明中也会忽略
never:
type Temp = 1 | 'a' | never
// 得到
// type Temp = 1 | 'a'
Type ne = never
// 得到
// type ne = never
用在函数类型时,TypeScript 的 extends 条件类型,封装 Filter<T, U> 函数类型就用到了 never 类型的返回值的特性:
type Filter<T, U> = T extends U ? never : T;
type Values = Filter<"x" | "y" | "z", "x">;
// 得到 type Values = "y" | "z"
理解这些类型后,之后理解官方预定于的高级类型。
TS类型-tuple
tuple是元组类型,很多语言中也有这种数据类型,比如Python、Swift等。
const tInfo: [string, number, number] = ["why", 18, 1.88];
const item1 = tInfo[0]; // why, 并且知道类型是string类型
const item2 = tInfo[1]; // 18, 并且知道类型是number类型
那么tuple和数组有什么区别呢?
首先,数组中通常建议存放相同类型的元素,不同类型的元素是不推荐放在数组中。(可以放在对象或者元组中)
其次,元组中每个元素都有自己特性的类型,根据索引值获取到的值可以确定对应的类型;
Tuples的应用场景
那么tuple在什么地方使用的是最多的呢?
tuple通常可以作为返回的值,在使用的时候会非常的方便;
function
函数的形参和实参
function 函数名(形参名:数据类型){
代码逻辑
}
函数名(实参)
//形参和实参 一一对应,类型也要对应
函数返回值
1、将函数内部计算的结果进行返回,以便使用该结果进行其他运算
在函数名()后面写 :返回值的数据类型
eg:function 函数名(): number{
return 15
}
//若没有指定函数的返回值,那么函数的返回值的默认类型为void(空,啥也没有)
1.当用变量接收函数返回值时,变量的类型和返回值的类型一致
2.直接使用函数的返回值进行其他运算
3.`在开发的过程中,一般不用去写函数返回值的类型注解(自动推导)`
2、终止代码执行,return后的代码不会被执行
3、return只能在函数内使用
4、可以单独使用,后面可以不跟内容,用来可以终止函数执行
5、箭头函数或匿名函数中的item的数据类型可以根据上下文环境推导出来,可以不进行添加
6、可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必需参数了;
TypeScript 会将添加了默认值的参数识别为可选参数,此时就不受前后位置的限制了
//设置默认值在确定参数前,
function fn2(num:number = 100, y:number){
return num+y
}
fn2(undefined,200) //要传两个参,否则会报错
fn2(null,200)
//设置的默认值在确定参数后,可以只传一个确定参数即可
7、剩余参数
使用ES6中的…,将函数的参数进行接收,并进行类型注解
function fn(...nums:number[]){
console.log(nums)
}
fn(1,2,3,4,5)
8、可推导的this类型
let obj={
fn(){}
}
obj.fn() //此时的this指的是obj
9、不确定的this类型
fucntion fn(){}
let obj={
fn,
}
obj.fn() //ts推导得来,此时的this指的是obj
10、确定的this指向
//给函数的第一个参数传入this,告诉ts,此时函数中的this指向的是谁
type obj={
name:string,
}
function hrl(this: obj,num:number) {//第二参数,进行类型注释,否则报错
console.log(this.name,num);
}
let obj5={
name:'lalal',
hrl,
}
obj5.hrl(123) //this指的是obj5
11、函数的重载
函数签名:通过函数签名的形式实现函数的重载
注意:有重载的情况下,即使数据类型是any,只要不符合任意一个重载函数别名,就不能使用重载
function sum(a1:number, a2:number): number;
function sum(a1:string, a2:string): string;
function sum(a1: any, a2: any){
return a1+a2
}
//在调用sum函数的时候,它会根据我们传入的参数类型,来决定执行函数时,到底执行哪一个函数签名
sum(1,2);//3
sum("1","2");//"12"
二、类型的补充
函数是JavaScript非常重要的组成部分,TypeScript允许我们指定函数的参数和返回值的类型。
参数的类型注解
声明函数时,可以在每个参数后添加类型注解,以声明函数接受的参数类型:
function sum(num1: number, num2: number) {
return num1 + num2
}
sum(123, 321)
函数的返回值类型
我们也可以添加返回值的类型注解,这个注解出现在函数列表的后面:
function sum(num1: number, num2: number):number {
return num1 + num2 //我们不仅规定了函数参数的类型,还规定了函数返回值的类型
}
sum(123, 321)
和变量的类型注解一样,我们通常情况下不需要返回类型注解,因为TypeScript会根据 return 返回值推断函数的返回类型:
某些第三方库处于方便理解,会明确指定返回类型,但是这个看个人喜好;
2.1 联合类型注解
(1)匿名函数的参数
匿名函数与函数声明会有一些不同,当一个函数出现在typeScript可以确定该函数会被如何调用的地方时,该函数的参数会自动指定类型,我们并没有指定item的类型,但是item是一个string类型,这是因为TypeScript会根据forEach函数的类型以及数组的类型推断出item的类型,这个过程称之为上下文类型,因为函数执行的上下文可以帮助确定参数和返回值的类型。
const names = ["abc", "cba", "nba"]
// item根据上下文的环境推导出来的, 这个时候可以不添加的类型注解
// 上下文中的函数: 可以不添加类型注解
names.forEach(function(item) {
console.log(item.split(""))
})
(2)参数类型为对象类型
如果我们希望限定一个函数的参数为对象类型,在对象里我们可以添加属性并且告知typeScript该属性需要是什么类型,属性之间可以使用 , 或者 ; 来分割,最后一个分隔符是可选的,每个属性的类型部分也是可选的,如果不指定,那么就是any类型。
function printPoint(point: {x: number, y: number}) {
console.log(point.x);
console.log(point.y)
}
printPoint({x: 123, y: 321})
2.2 可选类型
对象类型也可以指定哪些属性是可选的,可以在属性的后面添加一个?,当然我们在开发中建议函数如果有多个参数时,建议将可选类型放在参数的最后面。
function printPoint(point: {x: number, y: number, z?: number}) {
console.log(point.x)
console.log(point.y)
console.log(point.z)
}
printPoint({x: 123, y: 321})
printPoint({x: 123, y: 321, z: 111})
2.3 联合类型
typeScript允许我们使用多种运算符,从现有类型中构建新类型,我们来使用第一种组合类型的方法,联合类型,联合类型是由两个或者多个其他类型组成的类型,表示函数参数可以是这些类型中的任何一个类型,而联合类型中的每一个类型被称之为联合成员。但是在使用联合类型时还是需要注意类型缩小的问题。
function printID(id: number|string|boolean) {
// 使用联合类型的值时, 需要特别的小心narrow: 缩小
// TypeScript帮助确定id一定是string类型
if (typeof id === 'string') {
console.log(id.toUpperCase())
} else {
console.log(id)
}
}
printID(123)
printID("abc")
printID(true)
2.4 类型别名
在上面,我们通过在类型注解中编写对象类型或者联合类型,但是当我们想要多次在其他地方使用它们时,就要编写多次,因此我们可以给对象类型起一个别名:
type IDType = string | number | boolean
type PointType = {
x: number
y: number
z?: number
}
function printId(id: IDType) {}
function printPoint(point: PointType) {}
2.5 剩余参数
从ES6开始,JavaScript也支持剩余参数,剩余参数语法允许我们将一个不定数量的参数放到一个数组中。
function sum(initalNum: number, ...nums: number[]) {
let total = initalNum
for (const num of nums) {
total += num
}
return total
}
console.log(sum(20, 30))
console.log(sum(20, 30, 40))
console.log(sum(20, 30, 40, 50))
2.6 类型断言as
类型断言:可以用来手动指定一个值的类型。
有时候TypeScript无法获取具体的类型信息,这个我们需要使用类型断言(Type Assertions)。
语法:值 as 类型或者<类型>值
推荐使用值 as 类型语法,因为后者在jsx中有问题。
用途是将一个联合类型断言为其中一个类型
当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型中共有的属性或方法:
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function getName(animal: Cat | Fish) {
return animal.name;
}
而有时候,我们确实需要在还不确定类型的时候就访问其中一个类型特有的属性或方法,比如:
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function isFish(animal: Cat | Fish) {
if (typeof animal.swim === 'function') {
return true;
}
return false;
}
// index.ts:11:23 - error TS2339: Property 'swim' does not exist on type 'Cat | Fish'.
// Property 'swim' does not exist on type 'Cat'.
上面的例子中,获取 animal.swim 的时候会报错。
此时可以使用类型断言,将 animal 断言成 Fish:
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function isFish(animal: Cat | Fish) {
if (typeof (animal as Fish).swim === 'function') {
return true;
}
return false;
}
这样就可以解决访问 animal.swim 时报错的问题了。
需要注意的是,类型断言只能够「欺骗」TypeScript 编译器,无法避免运行时的错误,反而滥用类型断言可能会导致运行时错误:
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function swim(animal: Cat | Fish) {
(animal as Fish).swim();
}
const tom: Cat = {
name: 'Tom',
run() { console.log('run') }
};
swim(tom);
// Uncaught TypeError: animal.swim is not a function`
上面的例子编译时不会报错,但在运行时会报错:
Uncaught TypeError: animal.swim is not a function
原因是 (animal as Fish).swim() 这段代码隐藏了 animal 可能为 Cat 的情况,将 animal 直接断言为 Fish 了,而 TypeScript 编译器信任了我们的断言,故在调用 swim() 时没有编译错误。
可是 swim 函数接受的参数是 Cat | Fish,一旦传入的参数是 Cat 类型的变量,由于 Cat 上没有 swim 方法,就会导致运行时错误了。
总之,使用类型断言时一定要格外小心,尽量避免断言后调用方法或引用深层属性,以减少不必要的运行时错误。
其他例子
比如我们通过 document.getElementById,TypeScript只知道该函数会返回 HTMLElement ,但并不知道它具体的类型:
TypeScript只允许类型断言转换为 更具体 或者 不太具体 的类型版本,此规则可防止不可能的强制转换:
更具体的意思就是 string,number这种类型;
不太具体的意思就是 unknown这种类型。
2.7 非空类型断言
当我们在编写代码的时候, 在执行ts编译阶段会报错
非空断言 使用的!, 表示可以确定某个标识(变量)是有值的, 可以跳过ts在编译阶段对它的检测
确定我们传入的参数有有值的, 这个时候就可以使用非空类型断言
function fn(msg?:string){
//传入的msg有可能是为undefined的, 这个时候是不能执行uppercase方法
console.log(msg!.toUpperCase())
}
fn("hello")
2.8 可选链的使用
可选链 ES11(2020年) 中新增的特性 不是TS中独有的
当去读取一个对象的属性,但是这个属性不存在的时候(就会返回undefined),继续通过.的形式访问下一级(undefined.属性),就会出现报错
type Person = {
name: string,
friend?: {
name: string,
age?: number,
girlFriend?: {
name: string
}
}
}
console.log(obj.friend?.name);
console.log(obj.friend?.girlFriend?.name);
拓展: !! ??
!!用于转换数据类型
??空值合并操作符
let res=null
let msg=res??'hrl' //有值的话hrl,没的话null
2.9 字面量类型
字面量类型 只能赋值 字面量
let str:‘a’=‘a’
字面量结合联合类型:为了确保当前变量不会被其他一些非相关的值覆盖
type Color = "red" | "green"
let color: Color = "red"
color = "green"
//只能是这两个值
2.10 类型缩小
type narrowing
使用联合类型注解结合typeof检测数据类型中是否具有某个方法
使用typeof x="number"判断语句,缩小比声明时更小的类型,改变ts的执行路径(不再去检验除number之外的其他类型)
常见的类型保护:typeof instanceof
常见类型比较:=== == !== != switch case
js中的in运算符:来确定当前的对象是否有该属性;若执行的属性存在这个对象或对象的原型链中,返回的都true
type obj{
name:string
}
name in obj //true
Object.prototype.age=12
age in obj //true
ts中的in:用于判断某个属性是否存在于指定类型中
type Cat = {
name: string;
run: ()=>void;
}
type Fish = {
name: string;
swim: ()=>void;
}
function move(animal: Cat | Fish) {
if("run" in animal){
animal.run()
}else {
animal.swim()
}
}
let cat:Cat = {
name: "小猫",
run(){}
}
let fish:Fish = {
name: "小鱼",
swim(){}
}
move(cat)
2.11 交叉类型
交叉类型(intersection Types)合并,表示需要满足多个条件,使用&符隔开;表示的含义是需要同时满足配置的数据类型,不可能有这样的数据,所以可以将看做为never
type obj=number & string