TypeScript基本介绍
TS官方文档:www.typescriptlang.org/
中文文档(不再维护):www.tslang.cn/
TypeScript简称TS,是JavaScript的超集,简单来说就是:JS有的TS都有。
为什么要有typescript?
- 背景:JS 的类型系统存在“先天缺陷”弱类型,JS 代码中绝大部分错误都是类型错误(Uncaught TypeError)
- 这些经常出现的错误,导致了在使用 JS 进行项目开发时,增加了找 Bug、改 Bug 的时间,严重影响开发效率
原理分析
-
从编程语言的动静来区分,TypeScript 属于静态类型的编程语言,JavaScript 属于动态类型的编程语言
- 静态类型:
编译期做类型检查(早) - 动态类型:
执行期做类型检查(晚)
- 静态类型:
-
对于JS来说:需要等到代码真正去执行的时候才能发现错误
-
对于TS来说:在代码编译的时候(代码执行前)就可以发现错误
安装编译TS的工具包
- 因为node.js/浏览器。只认识JS代码,不认识TS代码
- 需要先将TS代码转化为JS代码,然后才能运行
安装步骤
-
执行安装指令
npm i -g typescriptyarn global add typescript- 注意:Mac 电脑安装全局包时,需要添加 sudo 获取权限:
sudo npm i -g typescript - yarn 全局安装:
sudo yarn global add typescript
2.查看安装是否成功
- tsc -v(查看typescript的版本)
类型注解
let sum:number = 10;
sum = 'hhh' // 报错
sum = 333 // 通过
-
代码中的
:number就是类型注解 -
类型注解可以为变量添加类型约束
- 一开始定义了什么类型,约定了以后只能给变量赋值该类型的值
- 如果赋值了其他类型的数据,就会报错
-
约定了类型之后,代码的提示就会非常的清晰,更便于后期找bug
类型概述
能够理解TypeScript中有哪些数据类型
说明
-
了解类型注解,就必须了解ts中一共有哪些类型
-
TS中的常用基础类型细分为两类:
- JS已有类型
- TS新增类型
JS已有类型
- 原始类型,简单类型(string、boolean、null、undefined、number)
- 复杂数据类型(数组、对象、函数)
TS新增类型
- 联合类型
- 自定义类型(类型别名)
- 接口
- 元组
- 字面量类型
- void
- 等等...
原始数据类型
- number / string / boolean / null / undefined
- 特点:简单,这些类型,完全按照 JS 中类型的名称来书写
let age: number = 18
let myName: string = '老师'
let isLoading: boolean = false
let obj: null = null
let und: undefined = undefined
// 注意点:
// null 类型的值只能是 null
// undefined 类型的值只能是 undefined
数组类型
数组类型的两种写法:
// 写法一:
let numbers: number[] = [1, 3, 5]
// 写法二:
let strings: Array<number> = [1, 3, 5]
// 推荐使用 `number[]` 写法
联合类型
定义:
- 如果定义的数据既可以是number类型,又可以是string类型
- 可以使用联合类型
语法:
let arr: (number | string)[] = [1, 'a', 3, 'b']
- 解释:|(竖线)在 TS 中叫做
联合类型,即:由两个或多个其他类型组成的类型,表示可以是这些类型中的任意一种。 - 注意:这是 TS 中联合类型的语法,只有一根竖线,不要与 JS 中的或(|| 或)混淆了
// 定义一个变量,既可以是 number 类型,又可以是 string 类型
let foo: number | string = 123
// 定义一个数组,数组中可以有数字或者字符串, 需要注意 | 的优先级
let arr: (number | string)[] = [1, 'abc', 2]
// 应用:定义一个定时器变量
let timer: number | null = null
timer = setInterval(() => {}, 1000)
类型别名
说明:
类型别名(自定义类型):为任意类型起别名- 使用场景:当同一类型(复杂)被多次使用时,可以通过类型别名,
简化该类型的使用
let arr1 : (number | string )[] = [1,'a',2,'b']
let arr2 : (number | string )[] = [5,'A',6,'B']
// 利用类型别名
type MyArray = (number | string)[]
let arr1: MyArray = [1, 'a', 3, 'b']
let arr2: MyArray = ['x', 'y', 6, 7]
- 使用
type关键字来创建自定义类型 - 类型别名(比如,此处的 MyArray)可以是任意合法的变量名称
推荐使用大写字母开头- 创建类型别名后,直接使用该类型别名作为变量的类型注解即可
函数类型-基本使用
说明:
-
函数的类型实际上指的是:
函数参数&返回值的类型
单独指定参数、返回值的类型:
// 函数声明
function test(num1: number, num2: number): number {
return num1 + num2
}
// 箭头函数
const test = (num1: number, num2: number): number => {
return num1 + num2
}
函数类型-指定整个函数的类型
说明:
- 函数也可以直接指定整个函数的类型
- 应用:如果要封装计算方法(+,-,*,/)
指定整个函数的类型
type Addfn = (num1:number , num2:number)=>number
const add:Addfn= (num1,num2)=>{
return num1 + num2
}
- 当函数作为表达式时,可以通过类似箭头函数形式的语法来为函数添加类型
- 注意:这种形式只适用于
函数表达式
函数类型-void类型
说明
- 如果函数没有返回值,那么函数的返回值类型为:
void
function test(name:string):void{
console.log('hello',name)
// 只依赖该函数的一些操作,而不依赖该函数的返回值
}
注意:如果一个函数没有返回值,此时,在TS的类型中,应该使用void类型
// 如果什么都不写,此时,add 函数的返回值类型为: void
const add = () => {}
// 这种写法是明确指定函数返回值类型为 void,与上面不指定返回值类型相同
const add = (): void => {}
// 但是,如果指定 返回值类型为 undefined,此时,函数体中必须显示的 return undefined 才可以
const add = (): undefined => {
// 此处,返回的 undefined 是 JS 中的一个值
return undefined
}
函数类型-可选参数
说明:
- 使用函数实现某个功能时,参数也可以传也可以不传。
- 这种情况下,在给函数参数指定类型时,就用到
可选参数了 - 比如,数组的slice方法,可以
slice()也可以slice(2)还可以slice(2,5)
function mySlice(start?: number, end?: number): void {
console.log('起始索引:', start, '结束索引:', end)
}
- 可选参数:在可传可不传的参数名称后面添加
?(问号) - 注意:
可选参数只能出现在参数列表的最后,也就是说可选参数后面不能再出现必选参数
对象类型-基本使用
说明:
- JS中的对象是由属性和方法构成的。
- 而
TS对象的类型就是描述对象的结构(有什么类型的属性以及方法)
语法:
// 空对象
let person: {} = {}
// 有属性的对象
let person: { name: string } = {
name: '同学'
}
// 既有属性又有方法的对象
// 在一行代码中指定对象的多个属性类型时,使用 `;`(分号)来分隔
let person: { name: string; sayHi(): void } = {
name: 'jack',
sayHi() {}
}
// 对象中如果有多个类型,可以换行写:
// 通过换行来分隔多个属性类型,可以去掉 `;`
let person: {
name: string
sayHi(): void
} = {
name: 'jack',
sayHi() {}
}
注意
- 使用
{}来描述对象的结构 - 属性采用
属性名:类型的形式 - 方法采用
方法名():返回值类型的形式
对象类型-箭头函数参数类型
指定学生的类型
- 属性:姓名、性别、成绩、身高
- 方法:学习 打机
方法的类型也可以使用箭头函数形式
{
greet(name: string):string,
greet: (name: string) => string
}
type Person = {
greet: (name: string) => void
greet(name: string):void
}
let person: Person = {
greet(name) {
console.log(name)
}
}
13、对象类型-对象可选属性
说明:
- 对象的属性或方法,也可以是可选的,此时就用到
可选属性了 - 比如,我们在使用 axios({ ... }) 时,如果发送 GET 请求,method 属性就可以省略
- 可选属性的语法与函数可选参数的语法一致,都使用
?来表示
type Config = {
url: string
method?: string
}
function myAxios(config: Config) {
console.log(config)
}
对象类型-使用类型别名
定义两个学生对象
- 属性:姓名 性别 成绩 身高
- 方法:学习 打游戏
// 创建类型别名
type Person = {
name:string,
sayHi():void
}
// 使用类型别名作为对象的类型
let person :Person = {
name:'jack',
say(){}
}
接口类型-基本使用
说明:
- 当一个对象类型被多次使用时,可以使用type来描述对象的类型,达到复用类型的目的
- 也可以使用
接口( interface )
接口
- 使用
interface关键字来声明接口 - 接口名称(比如,此处的 IPerson),可以是任意合法的变量名称,推荐以
I开头 - 声明接口后,直接使用接口名称作为变量的类型
- 因为每一行只有一个属性类型,因此,属性类型后没有 ;(分号)
interface IPerson {
name:strng,
age:number,
sayHi():void
}
const obj : IPerson = {
name: 'jack',
age: 19,
sayHi() {}
}
接口类型--- interface vs type
interface(接口) 和 type (类型别名)的对比
-
相同点:都可以给对象指定类型
-
不同点:interface(接口):只能为对象指定类型
-
type(类型别名):
- 不仅可以为对象指定类型
- 还可以为任意类型指定别名
-
推荐:能使用type就是用type
interface IPerson {
name: string
age: number
sayHi(): void
}
// 为对象类型创建类型别名
type IPerson = {
name: string
age: number
sayHi(): void
}
// 为联合类型创建类型别名
type NumStr = number | string
接口类型-接口继承
作用:
- 如果两个接口之间如果由相同的属性或方法,可以将
公共的属性或方法抽离出来,通过继承来实现复用。 - 比如:这两个接口都有x、y两个属性,重复写两次,可以,但很繁琐。
interface Point2D{
x:number
y:number
}
interface Point3D{
x:number
y:number
z:number
}
- 利用继承
interface Point2D{
x:number
y:number
}
// 继承Point2D接口类型
interface Point3D extends Point2D{
z:number
}
总结:
-
使用
extends( 继承 ) 关键字实现了接口Point3D继承了Point2D -
继承后Point3D就有了 Point2D 的所有属性和方法
- 这个时候,Point3D 同时有 x、y、z 三个属性
元组类型
- 在地图中,使用经纬度坐标来标记位置信息
- 可以使用数组来记录坐标,那么,该数组中只有两个元素,并且这两个元素都是数值类型
let position:number[] = [111.20,33,55]
- 使用number[]的缺点:不严谨的,因为该类型的数组中可以出现任意多个数字
- 更好的方法:
元组 Tuple - 元组类型是另一种类型的数组,它确切地知道包含多少个元素,以及特定索引对应的类型
let position: [number, number] = [39.5427, 116.2317]
注意:
- 元组类型可以确切地标记出有多少个元素,以及每个元素的类型
- 该示例中,元组有两个元素,每个元素的类型都是number
类型推论
说明:
-
在TS中,某些没有明确指出类型的地方,
TS的类型推论机制会帮助提供类型 -
换句话说:由于类型推论的存在,这些地方,类型注解可以省略不写
- 声明变量并初始化时
- 决定函数返回值
// 变量 age 的类型被自动推断为:number
let age = 18
// 该函数返回值的类型被自动推断为:number
function add(num1: number, num2: number) {
return num1 + num2
}
注意
- 能
省略类型注解的地方就省略(偷懒,充分利用TS类型推论的能力hhhh,提高开发效率) - 技巧:如果不知道类型,可以通过鼠标放在变量名称上,利用VSCode的提示来查看类型
字面量类型-基本使用
let str1 = 'Hello TS' // ===> 推断为string类型
const str2 = 'Hello TS' // ===> 推断为 Hello TS类型
这里为什么str1 和 str2 不都是 string类型呢?
str1是一个变量(let),它的值可以是任意字符串的,所以类型为:string
str2是一个变量 (const),它的值是不能变化的,只能是'Hello TS',所以,它的类型为:'Hello TS'
-
注意:此处的'Hello TS',就是一个
字面量类型,也就是说某个特定的字符串也可以作为TS中的类型 -
任意的JS字面量( 比如,对象、数字等 )都可以作为类型使用
- 字面量:
{name:'yang'} [] 18 20 'abc' false function () {}
字面量类型-使用模式和场景
说明
- 使用模式:
字面量类型配合类型一起使用 - 使用场景::用来表示一组明确的可选值列表
比如:
- 性别: 男 / 女
- axios的请求方式: get / post / put / delete / patch
// 使用自定义类型:
type Direction = 'get' | 'post' | 'delete' | 'patch'
function axios (method:Direction){
console.log(method)
}
// 调用函数时,会有类型提示
axios('post')
结论:
- 相对于string类型,使用字面量类型更加精确、严谨
any类型
说明:
-
原则:不推荐使用
any-
这会让TypeScript变为
AnyScript(失去TS类型保护的优势) -
因为当值的类型为any时,可以对该值进行任意操作,并且不会有代码提示
let obj: any = { x: 0 } obj.bar = 100 obj() const n: number = obj
注意
-
以上操作都不会有任意类型错误提示,即使可能存在错误
-
这样就失去了TS给我们带来的类型判断作用了
-
尽可能的避免使用
any类型:以下这两种情况此外- 除非临时使用
any来避免书写很长、很复杂的类型 - 使用时不关注数据的类型(如:console.log())
- 除非临时使用
-
其他隐式具有any类型的情况
- 声明变量不提供类型也不提供默认值
- 函数参数不加类型
-
注意: 因为不推荐使用any,所以,这两种情况下都应该提供类型
-
类型断言-基本使用
- 定义一个变量,默认存放空对象
- 对象中的属性来源于网络请求
- 使用对象中的属性
有时候你会比 TS 更加明确一个值的类型,此时,可以使用类型断言来指定更具体的类型。
type Use = {
name:string
age:sting
gender:sting
}
// 指定类型之后,直接给use赋值空对象会报错
// 但是我们是明确知道 use对象就是Use类型
// 可以通过 as 来将空对象转为use,以达到显示提示的目的
let use:Use = {} as Use
// 写法二
let use:Use = <Use> {}
setTimeout(() => {
use = {
name: 'zs',
age: 18,
gender: '男'
}
}, 1000);
console.log(use.name)
类型断言-操作dom
- 类型断言还在dom操作时用的比较多
// 直接获取 dom 之后,无法点出 link 中的属性
const aLink = document.getElementById('link')
// 获取 dom 之后,将 link 断言为HTMLAnchorElement,可以轻松点出属性
const aLink = document.getElementByid('link') as HTMLAnchorElement
泛型-泛型函数
定义一个函数:
- 函数的参数为Number类型时,函数的返回值也为Number类型
- 函数的参数为 String 类型时,函数的返回值也为 String 类型
function fn<Type>(value:Type) :Type {
return value
}
const res1 = fn<Number>(1)
const res2 = fn<String>('abc')
// 简化:调用时可以省略类型
const res1 = fn(1)
const res2 = fn('abc')
说明:
- :就是泛型。
- 泛型:是可以在保证类型安全前提下,让函数等与多种类型一起工作,从而实现复用,常用于:函数、接口、class 中。
- ”Type“:只是一个类型的占位,可以使用其他名称来替换。如:”T“
- 在调用泛型函数时,
可以省略<类型>来简化泛型函数的调用
注意
- 语法:在函数名称的后面添加
<>,尖括号中添加类型变量,比如此处的Type 类型变量Type,是一种特殊类型的变量,它处理类型而不是值该类型变量相当于一个类型容器,能够捕获用户提供的类型(具体是什么类型由用户调用该函数时指定)- 因为Type是类型,因此可以将其作为函数参数和返回值的类型,表示参数和返回值具有想用的类型
- 类型变量Type,可以是任意合法的变量名称
- 可以在调用泛型函数时,省略类型参数
泛型-泛型约束
-
定义一个函数
- 函数的参数为某一类型时,函数的返回值也为该类型
- 参数中的要有一个属性:length
interface ILenth {
length:number
}
function fn<T extends ILength> (value :T) :T {
console.log(value.length)
return value
}
// const res1 = fn<Number>(1) // 报错
const res2 = fn('abc')
const res3 = fn([1, 2, 3])
const res4 = fn({
length: 11
})
说明
- 默认情况下,泛型函数的类型变量Type可以代表多个类型,这导致无法访问任何属性
- 可以使用泛型约束,给传入的泛型添加限制条件
泛型 - 泛型接口
- 泛型接口:接口也可以配合泛型来使用,以增加其灵活性,增强其复用性
interface IUse<Type> {
name : Type
age: number
gender: Type
sayHi():Type
}
let obj : IUse <string> = {
name:'zs',
age:18,
gender:'男',
sayHi(){
return this.name
}
}
注意:
- 在接口名称的后面添加
<类型变量>,那么,这个接口就变成了泛型接口。 - 接口的类型变量,对接口中所有其他成员可见,也就是
接口中所有成员都可以使用类型变量。 - 使用泛型接口时,
需要显式指定具体的类型