学习TypeScript
TypeScript初识
JavaScript没有类型检测,这会让我们的代码不安全,TS可以很好的解决这个问题
TS简介
Typescript是拥有类型的JavaScript超集
JavaScript所拥有的特性,TS全部都支持,并且在语言层面上添加了类型约束,还加上一些语法的扩展
TS的编译环境
TS最终还是会被编译成JS代码运行,所以我们需要搭建对应的环境
在电脑上安装Typescript,这样就可以通过TypeScript的Compiler将其编译成JavaScript
全局安装:npm install typescript -g
查看版本:tsc --version
TS的运行环境
运行的两个步骤:
tsc xxx.ts
把ts文件编译成JS代码- 在浏览器或Node环境下运行JS代码
如果每次都要做这两个步骤,那就太麻烦了
有什么简化的方式呢?
第一种:通过webpack,配置本地的TS编译环境和开启一个本地服务,可以直接运行在浏览器上
第二种:通过ts-node库,为TS的运行提供执行环境
安装 ts-node:
npm install ts-node -g
另外 ts-node需要安装依赖 tslib 和 @types/node 两个包
npm install tslib @types/node -g
然后可以直接通过 ts-node 来运行 TS 的代码:
ts-node xxx.ts
变量的声明
定义的时候给标识符加类型: var/let/const 标识符: 数据类型 = 赋值
但是 var 不推荐使用
另外注意 string 和 String 的区别
string是TypeScript中定义的字符串类型,String是ECMAScript中定义的一个包装类
let message: string = "abc"
console.log(message);
const name: string = 'xxx'类型推断(推导),就是说我们第一次给变量赋值的时候,会根据这个赋值的内容,自动推断变量的类型
let num = 2 // 自动类型推断为number类型
num = "123" // 报错
推荐:如果可以自动推导出变量的类型的时候,不加类型
不确定类型的时候,要自己加上类型
数据类型
我们常说TS是JS的一个超集
JS类型
TS和JS都有的数据类型
- number类型
TS 和 JS一样
let num: number = 123 |
- boolean类型
true、false
let flag: boolean = truelet flag: boolean = true |
- string类型
let message: string = "hello" |
- Array类型
固定数组里面存放的数据类型
const name1: string[] = [] // 推荐 |
- Object类型
const info:object = { // 在这里设置了object类型 |
设置了object类型不能获取数据也不能设置数据
如果去掉了:object就可以获取,并且里面的属性类型也有推断
const info = { |
- Symbol类型
跟 JS 一样,主要用于设置唯一属性名
let title1: symbol = Symbol("title") |
- Null 和 Undefined类型
null:
let n1: null = null |
同理:undefined:
let n3: undefined = undefined |
TypeScript类型
any类型
any表示任意类型
在不确定变量类型的时候,可以使用any类型,但是一旦使用了,意味着我们可以对any类型的变量进行任何的操作,包括赋值任何类型的值;获取不存在的属性、方法
非常不安全,不推荐使用
let message: any = "hello" |
unknown类型
用于描述类型不确定的变量
unknown类型只能赋值给any和unknown类型
function foo() { |
void类型
void通常来指定一个函数没有返回值的,那么它的返回值就是void类型
另外,这个函数可以返回 null,undefined
一般都不写的,因为没有返回值默认推断就是void
function sum(num1: number, num2: number): void { |
never类型
表示永远不会发生值的类型
never表示函数用于执行不到返回值那一步(抛出异常或死循环)的返回值类型
而void是函数没有返回值,可以返回null,undefined
function foo() { |
never 有什么应用场景?
// function handleMessage(message: string | number ) { |
tuple类型
tuple 是元组类型,元组中每个元素都有自己特定的类型,根据索引值获取到的值可以确定对应的类型
const info: [string, number, number] = ["xxx", 18, 1.88] |
而数组通常建议存放相同类型的值
tuple应用场景
tuple通常可以作为返回的值,在使用的时候会非常的方便
function useState<T>(state: T) { |
函数的参数和返回值类型
// 一般可以不写返回值的类型,会自动推断 |
匿名函数的参数
const names = ["aaa", "bbb", "ccc"] |
对象类型
如果我们希望限定一个函数接收的参数是一个对象
function printPoint(point: {x: number, y: number}) { |
联合类型
function printId(id: number | string) { |
使用联合类型
function printID(id: number | string) { |
可选类型
对象类型也可以指定哪些属性是可选的,可以在属性后面加一个?
function printPoint(point: {x: number, y:number, z?:number}) { |
那么这个属性可传可不传
另外,可选类型可以看做是类型和undefined的联合类型
function foo(message?: string) { |
类型别名
我们可以给对象类型起一个别名,方面我们后续使用
用到了关键字 type
type IDType = string | number |
类型断言 as
将类型转换为更具体的类型
本来不加类型断言的话,TS只会把el推断为HTMLElement类型
const el = document.getElementById("xxx") as HTMLImageElement |
案例2:Person 是 Student 的父类
function sayHello(p: Person) { |
非空类型断言!
! 用于确定某个标识符是有值的,跳过 ts 在编译阶段时对它的检测
// message 可以是string/undefined |
可选链
实际上是 ES11 增加的特性
?.
操作符,作用是当对象的属性不存在时,会短路,直接返回undefined,如果存在,那么才会继续执行
type Person = { |
?? 和 !! 运算符
?? 是 ES11新增的特性
**空值合并操作符 ?? **,是一个逻辑操作符,当操作符的左侧是 null 或者 undefined 时,返回其右侧操作数, 否则返回左侧操作数
const message = null |
!!操作符,将一个其他类型转换成boolean类型,类型Boolean(变量)
const message = "" |
字面量类型
字面量也可以当做类型
let msg:"hello" = "hello" // 但是msg的值只能是"hello" |
那有什么意义呢?可以将多个类型联合在一起
type Alignment = 'left' | 'right' | 'center' |
字面量推理
下面的代码,默认情况下info 进行类型推断的时候,method是string类型
加上as const 后,methods就是"GET"字面量类型
const info = { |
类型缩小
Type Narrowing 类型缩小
可以通过类似于 typeof padding === “number” 的判断语句,来改变TypeScript的执行路径
在给定的执行路径中,缩小比声明时更小的类型,这个过程称之为 缩小
而我们编写的typeof padding === "number
可以称之为类型保护
常见的类型保护:
typeof
type IDType = number | string
function printID(id: IDType) {
// 这外面使用id是 IDtype 类型
if (typeof id === 'string') {
// 这里面确认id是string类型
console.log(id.toUpperCase());
} else {
// 确认是number类型
}
}平等缩小(=== == !== !=/switch)
type Direction = "left" | "right" | "top" | "bottom"
function printDirection(direction: Direction) {
// 1.if判断
// if (direction === 'left') {
// console.log(direction)
// } else if ()
// 2.switch判断
switch (direction) {
case 'left':
console.log(direction)
break;
// case ...
}
}instanceof
class Student {}
class Teacher {}
function work(p: Student | Teacher) {
if (p instanceof Student) {
} else {
}
}in
属性是否存在某对象上
type Fish = {
swimming: () => void
}
type Dog = {
running: () => void
}
function walk(animal: Fish | Dog) {
if ('swimming' in animal) {
} else {
animal.running()
}
}
const fish:Fish = {
swimming() {}
}
walk(fish)
TS函数类型
定义常量时,编写函数的类型
(num1: number, num2: number) => number 就是一个函数类型,并接收两个参数num1,num2,并且都是number类型
type AddFnType = (num1: number, num2: number) => number
const add: AddFnType = (a1: number, a2: number) => {
return a1 + a2
}函数作为参数时,在参数中如何编写类型?
function foo() {}
type FooFnType = () => void
function bar(fn: FooFnType) {
fn()
}
bar(foo)参数的可选类型
function foo(x: number, y?: number) {} // y可选
默认参数
function foo(x: number, y: number = 6) {}
剩余参数
将一个不定数量的参数放到一个数组中
function sum(...nums: number[]) {
let total = 0
for (const num of nums) {
total += num
}
return total
}
this类型
当this不确定的时候,通常TS会要求我们明确的指定this的类型
如果不指定的话,非常不安全,因为有可能直接调用函数或者通过别的对象来调用函数
type thisType = { name: string } |
函数的重载
函数的重载在实际开发也不是必须要用的,如果本来可以用联合类型简单实现的话,优先选择联合类型
例如有一个需求,希望对字符串和数字类型进行相加
function add(a1: number | string, a2: number | string) { |
以上通过联合类型的方式实现有两个缺点:
- 进行很多的逻辑判断(类型缩小)
- 返回值的类型依然不确定
函数的重载是什么?
函数的名称相同,但是参数不同的几个函数就是函数的重载,并且是没有函数执行体的
// 构成函数重载 |
以上案例确实使用函数重载实现更方便,所以可以选择函数重载
TS类的使用
这部分,实际开发中相对用的少一点,所以笔记暂时记的比较粗糙
类的定义
class Person { |
类的继承 extends super 关键字
class Person { |
类的多态
不同的数据类型进行同一个操作,表现出不同的行为,就是多态的体现
类的成员修饰符
public
共有的,默认编写的属性就是public
private
仅在同一类中可见
class Person {
private _name: string = ""
constructor(name: string) {
this._name = name
}
// 访问器setter/getter
set name(newName) {
this._name = newName
}
// getter
get name() {
return this._name
}
}其中 setter/getter 是访问器属性,这样外界就可以存储
const p = new Person("xxx")
p.name // 会调用get访问器
p.name = "kkk" // setprotected
仅在类自身及子类中可见、受保护的属性和方法
只读属性readonly
只读属性是可以在构造器中赋值, 赋值之后就不可以修改
属性本身不能进行修改, 但是如果它是对象类型, 对象中的属性是可以修改
class Person { |
静态成员
通过关键字static来定义,直接通过类调用
class Student { |
抽象类abstract
抽象类:
- 以abstract 声明的类是抽象类
- 抽象类和其他类区别不大,但是不能被实例化(不能通过new调用)
- 抽象类就是专门用来被继承的类
抽象方法:没有具体实现(没有方法体),抽象方法必须存在于抽象类中
function makeArea(shape: Shape) { |
类的类型
类本身可以作为一个类型
class Person { |
TS接口的使用
除了可以通过type开声明一个对象类型,还可以通过接口来声明
interface Point { |
另外,接口中也可以定义可选属性,只读属性
interface IInfoType { |
索引类型
interface IndexLanguage { |
函数类型
用过接口interface来定义函数类型
interface CalcFunc { |
当然还是推荐使用类型别名来定义函数
type CalcFunc = (num1: number, num2: number) => number |
接口继承
接口和类一样可以进行继承
interface ISwim { |
接口的实现
implements 用于指定 class 满足某个接口,而且类可以实现多个接口
不是很懂
面向接口开发
interface ISwim { |
交叉类型
一种类型合并,表示需要满足多个类型的条件
type MyType = number & string |
interface 和 type 区别
interface可以重复对某个接口来定义属性和方法
而type定义的是别名,别名是不能重复的
字面量赋值
interface IPerson { |
这是因为TypeScript在字面量直接赋值的过程中,为了进行类型推导会进行严格的类型限制。
但是之后如果我们是将一个 变量标识符赋值给其他的变量时,会进行freshness擦除操作
TS枚举类型
枚举其实就是将一组可能出现的值,一个个列举出来,定义在一个类型中,这个类型就是枚举类型
枚举允许开发者定义一组命名常量,常量可以是数字、字符串类型
定义枚举类型的关键字:enum
enum Direction { |
TS泛型
非常重要!
基本使用
简单来说就是,类型参数化
类型决定在调用函数的时候
基本使用:多加一个<Type>
参数来接收类型
function sum<Type>(num1: Type):Type { |
还有一种调用方式,类型推导
sum(50) // 那么类型Type就是字面量50 |
另外,还可以传入多个类型
function foo<T, E, O>(arg1: T, arg2: E, arg3: O, ...args: T[]) { |
开发时常用的名称:
T:Type的缩写,类型
K、V:key和value的缩写,键值对
E:Element的缩写,元素
O:Object的缩写,对象
泛型接口
定义接口中使用泛型
interface IPerson<T> { |
泛型类
定义类使用泛型
class Person<T1, T2> { |
泛型约束
给类型参数一点约束
关键字 extends
interface ILength { |
TS其他内容
模块化开发
TS有两种方式控制作用域
模块化
支持ES Module,CommonJS
命名空间namespace
是将一个模块内部再进行作用域的划分,防止一些命名 冲突的问题
// 为了其他文件能使用,这里还要导出 |
TS早期的东西,个人还是选择模块化
类型的查找
在之前,我们除了自己编写类型,还有用到一些其他的类型,比如说
document.getElementById("image") as HTMLImageElement |
HTMLImageElement 类型来自哪里呢?
首先我们需要知道一种ts文件:.d.ts
为后缀名的文件
- 这种文件是用来做类型的声明的(declare)。它仅仅用来做类型检测,告诉typescript我们有哪些类型
- 这种文件是不需要转成js文件来运行的
那么typescript会在哪里查找我们的类型声明呢?
有三种:
内置类型声明
typescript自带的,帮助我们内置了JS运行时的一些标准化API的声明文件
比如如Math、Date等内置类型,也包括DOM API,比如Window、Document等
内置类型声明通常在我们安装typescript环境中会带有
github:https://github.com/microsoft/TypeScript/tree/main/lib
外部定义类型声明
通常是我们使用一些库时,需要的一些类型声明
这些库有两种类型声明方式
- 在自己库中进行类型声明(编写.d.ts文件),比如axios
- 通过社区的一个公有库DefinitelyTyped存放类型声明文件
github链接:
该库的GitHub地址:https://github.com/DefinitelyTyped/DefinitelyTyped/
该库查找声明安装方式的地址:https://www.typescriptlang.org/dt/search?search=
比如我们需要安装react的类型声明,就可以去查找安装方式
自定义类型声明
比如我们使用的第三方库是一个纯的JavaScript库,没有对应的声明文件;比如lodash
这时候我们要自己声明类型文件:
任意一个项目文件夹下定义
.d.ts
文件,ts会自己找到的声明模块关键字:declare
// 声明模块
declare module 'lodash' {
export function join(arr: any[]): void
}
声明变量-函数-类
在.d.ts
文件中声明
declare let myName: string |
在其他地方使用:
let myName = "xxx" |
声明模块
声明模块的语法: declare module ‘模块名’ {}。
在声明模块的内部,可以通过export导出对应库的类、函数等
declare module 'lodash' { |
声明文件
文件会被当成模块使用,然后就可以引入
declare module '*.jpg' |
在别的地方import
使用
import img from './img/xxx.jpg' |
声明命名空间
比如在index.html中直接引入jQuery
然后在声明文件中(.d.ts
)
declare namespace $ { |
然后就可以在别的文件使用
$.ajax({...}) |