# TypeScript 代码:有明确的类型,即:number(数据类型)
let age: number = 19
# JavaScript 代码:无明确的类型
let name = '李斯'
背景:JS 的类型系统存在先天缺陷,JS 代码中绝大部分错误都是类型错误(Uncaught TypeError)
问题:增加了找 Bug、改 Bug 的时间,严重影响开发效率
从编程语言的动静来区分,TypeScript 属于静态类型的编程语言,JS 属于动态类型的编程语言
静态类型:编译期做类型检查;动态类型:执行期做类型检查
代码编译和代码执行的顺序:1 编译 2 执行
对于 JS 来说:需要等到代码真正去执行的时候才能发现错误(晚)
对于 TS 来说:在代码编译的时候(代码执行前)就可以发现错误(早)
并且,配合 VSCode 等开发工具,TS 可以提前在编写代码的同时就发现代码中的错误,减少找 Bug、改 Bug 时间
除此之外,Vue3 源码使用 TS 重写、Angular 默认支持 TS、React 与 TS 完美配合,TypeScript 已成为大中型前端项目的首先编程语言
问题:为什么要安装编译 TS 的工具包
回答:Node.js/浏览器,只认识 JS 代码,不认识 TS 代码。需要先将 TS 代码转化为 JS 代码,然后才能运行
安装命令:npm i -g typescript
typescript 包:用来编译 TS 代码的包,提供了 tsc 命令,实现了 TS -> JS 的转化
验证是否安装成功:tsc -v (查看 typescript 的版本)
说明:所有合法的 JS 代码都是 TS 代码
注意:由 TS 编译生成的 JS 文件,代码中就没有类型信息了
问题描述:每次修改代码后,都要重复执行两个命令,才能运行 TS 代码,太繁琐
简化方式:使用 ts-node 包,直接在 Node.js 中执行 TS 代码
安装命令:npm i -g ts-node(ts-node 包提供了 ts-node 命令)
使用方式:ts-node hello.ts
解释:ts-node 命令子在内部将 TS -> JS,然后再运行 JS 代码
let age: number = 18
说明:代码中的:number
就是类型注解
作用:为变量添加类型约束。比如,上述代码中,约定变量 age 的类型为 number(数值类型)
解释:约定了什么类型,就只能给变量赋值该类型的值,否则,就会报错
特点:简单,这些类型完全按照 JS 中类型的名称来书写
let age: number = 18
let name: string = '李广'
let loading: boolean = false
let numbers: number[] = [1, 3, 6]
let strings: Array = ['a', 'b', 'c']
需求:数组中既有 number 类型,又有 string 类型,这个数组的类型应该如何写
let arr: (number | string)[] = [1, 'a', 3, 'b']
解释:|(竖线)在 TS 中叫做联合类型,由两个或多个其他类型组成的类型,表示可以是这些类型中的任意一种
注意:这是 TS 中联合类型的语法,只有一根竖线,不要与 JS 中的或(||)混淆了
类型别名(自定义类型):为任意类型起别名
使用场景:当同一类型(复杂)被多次使用时,可以通过类型别名,简化该类型的使用
type CustomArray = (number | string)[]
let arr1: CustomArray = [1, 'a', 'b']
let arr2: CustomArray = ['x', 'y', 1]
说明;
函数的类型实际上指的时:函数参数和返回值的类型
为函数执行类型的两种方式:1. 单独指定参数、返回值的类型 2. 同时指定参数、返回值的类型
function add(num1: number, num2: number): number {
return num1 + num2
}
const add = (num1: number, num2: number): number => {
return num1 + num2
}
const add: (num1: number, num2: number) => number = (num1, num2) => {
return num1 + num2
}
解释:当函数作为表达式时,可以通过类似箭头函数形式的语法来为函数添加类型
注意:这种形式只是用于函数表达式
function greet(name: string): void {
console.log('Hello', name)
}
function mySlice(start?: number, end?: number): void {
console.log('起始索引:', start, '结束索引:', end)
}
可选参数:在可传可不传的参数名称后面添加?(问好)
注意:可选参数只能出现在参数列表的最后,也就是说可选参数后面不能再出现必选参数
JS 中的对象是由属性和方法构成的,而 TS 中对象的类型就是再描述对象的结构(有什么类型的属性和方法)
let person: { name: string; age: number; sayHi(): void } = {
name: 'jack',
age: 19,
sayHi() {}
}
解释:
;
(分号)来分隔;
(分号)可选属性:
对象的属性或方法,也可以是可选的,此时就用到可选属性了。
比如,在使用 axios({…}),如果发送 GET 请求,method 属性就可以省略
可选属性的语法与函数可选参数的语法一致,都是用 ?(问好)来表示
function myAxios(config: { url: string; method?: string}) {
console.log(config)
}
当一个对象类型被多次使用时,一般会使用接口(interface)来描述对象的类型,达到复用的目的
解释:
;
(分号)# 声明接口
interface IPerson {
name: string
age: number,
sayHi(): void
}
# 使用接口
let person: IPerson = {
name: 'jack',
age: 19,
sayHi() {}
}
interface(接口)和 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 }
interface Point3D extends Point2D { z: number }
解释:
场景:在地图中,使用经纬度坐标来标记位置信息
可以使用数组来记录坐标,那么,该数组中只有两个元素,并且这两个元素都是数值类型
let position: number[] = [39.5427, 116.2317]
使用 number[]的缺点:不严谨,因为该类型的数组可以出现任意多个数字
更高的方式:元组(Tuple)
元组类型是另一种类型的数组,它确切地知道包含多少个元素,以及特定索引对应地类型
let position: [number, number] = [39.5427, 116.2317]
解释:
let age = 18
function add(num1: number, num2: number) { return num1 + num2 }
注意:这两种情况下,类型注解可以省略不写
推荐:能省略类型注解的地方就省略
技巧:如果不知道类型,可以通过鼠标放在变量名称上,利用 VSCode 的提示来查看类型
有时候你会比 TS 更加明确一个值的类型,此时,可以使用类型断言来指定更具体地类型
百度
const aLink: HTMLElement
const aLink = document.getElementById('link')
注意:getElementById 方法返回值地类型是 HTMLElement,该类型只包含所有标签公共地属性或方法,不包含 a 标签特有地 href 等属性
因此,这个类型太宽泛(不具体),无法操作 href 等 a 标签特有地属性或方法
解决方式:这种情况下就需要使用类型断言指定更加具体地类型
使用类型断言:
const aLink: HTMLAnchorElement
const aLink = document.getElementById('link') as HTMLAnchorElement
解释:
技巧:在浏览器控制台,通过 console.dir() 打印 DOM 元素,在属性列表的最后面,即可看到该元素的类型
思考以下代码,两个变量的类型分别是什么?
let str1 = 'Hello TS'
const str2 = 'Hello TS'
通过 TS 类型推论机制,可以得到答案:
解释:
注意:此处的 ‘Hello TS’,就是一个字面量类型。也就是说某个特定的字符串也可以作为 TS 中的类型。除字符串外,任意的 JS 字面量(比如,对象、数字等)都可以作为类型使用
使用模式:字面量类型配合联合类型一起使用
使用场景:用来表示一组明确的可选值列表
function changeDirection(direction: 'up' | 'down' | 'left' | 'right') {
console.log(direction)
}
解释:参数 direction 的值只能是 up/down/left/right 中的任意一个
优势:相比于 string 类型,使用字面量类型更加精确、严谨
枚举的功能类似于字面量类型+联合类型组合的功能,也可以表示一组明确的可选值
枚举:定义一组命名常量。它描述一个值,该值可以是这些命名常量中的一个
enum Direction { Up, Down, Left, Right }
function changeDirection(direction: Direction) {
console.log(direction)
}
解释:
注意:形参 direction 的类型为枚举 Direction,那么,实参的值就应该是枚举 Direction 成员的任意一个
访问枚举成员:
enum Direction { Up, Down, Left, Right }
function changeDirection(direction: Direction) {
console.log(direction)
}
changeDirection(Direction.Up)
解释:类似于 JS 中的对象,直接用过点(.)语法访问枚举的成员
注意:枚举成员是有值的,默认为:从 0 开始自增的数值
枚举成员的值为数字的枚举,称为:数字枚举
# 给枚举中的成员初始化值:
enum Direction { Up = 10, Down, Left, Right }
enum Direction { Up = 2, Down = 4, Left = 8, Right = 16 }
字符串枚举:枚举成员的值是字符串
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT'
}
注意:字符串枚举没有自增长行为,因此,字符串枚举的每个成员必须有初始值
枚举是 TS 为数不多的非 JavaScript 类型扩展(不仅仅是类型)的特性之一。
因为其他类型仅仅被当作类型,而枚举不仅用作类型,还提供值(枚举成员都是有值的)
也就是说,其他的类型会在编译为 JS 代码时自动移除。但是,枚举类型会被编译为 JS 代码
var Direction;
(function (Direction) {
Direction["Up"] = "UP";
Direction["Down"] = "DOWN";
Direction["Left"] = "LEFT";
Direction["Right"] = "RIGHT";
})(Direction || (Direction = {}))
说明:
原则:不推荐使用 any!这会让 TypeScript 变为 “AnyScript”(失去 TS 类型保护的优势)
因为当值的类型为 any 时,可以对该值进行任意操作,并且不会有代码提示
let obj: any = { x: 0 }
obj.bar = 100
obj()
const n: number = obj
解释:
JS 中提供了 typeof 操作符,用来在 JS 中获取数据的类型
console.log(typeof "Hello World") // 打印 string
实际上,TS 也提供了 typeof 操作符:可以在类型上下文中引用变量或属性的类型(类型查询)
使用场景:根据已有变量的值,获取该值的类型,来简化类型书写
let p = { x: 1, y: 2}
function formatPoint(point: {x: number; y: number}) {}
formatPoint(p)
# 类型查询
function formatPoint(point: typeof p) {}
解释:
TypeScript 全面支持 ES2015 中引入的 class 关键字,并为其添加了类型注解和其他语法(比如,可见性修饰符)
class Person {}
const p: Person
const p = new Person()
解释:
构造函数:
class Person {
age: number
gender: string
constructor(age: number, gender: string) {
this.age = age
this.gender = gender
}
}
解释:
实例方法:
class Point {
x = 10
y = 10
scale(n: number): void {
this.x *= n
this.y *= n
}
}
解释: 方法的类型注解(参数和返回值)与函数用法相同
extends(继承父类):
class Animal {
move() { console.log('Moving along!')}
}
class Dog extends Animal {
bark() { console.log('汪!') }
}
const dog = new Dog()
解释:
implements(实现接口):
interface Singable {
sing(): void
}
class Person implements Singable {
sing() {
console.log('实现接口')
}
}
类成员可见性:可以使用 TS 来控制 class 的方法或属性对于 class 外的代码是否可见
可见性修饰符包括:public(公有的)、protected(受保护的)、private(私有的)
public:表示共有的、公开的,公有成员可以被任何地方访问,默认可见性
class Animal {
public move {
console.log('Moving along!')
}
}
解释:
protected:表示受保护的,仅对其声明所在类和子类中可见
class Animal {
protected move() { console.log('Moving along!') }
}
class Dog extends Animal {
bark() {
console.log('汪!')
this.move()
}
}
解释:
private:表示私有的,只在当前类中可见,对实例对象以及子类也是不可见的
class Animal {
private move() { console.log('Moving along!') }
walk() {
this.move()
}
}
解释:
readonly:表示只读,用来防止在构造函数之外对属性进行赋值
class Person {
readonly age: number = 18
constructor(age: number) {
this.age = age
}
}
解释:
两种类型系统:Structural Type(结构化类型系统)、Nominal Type System(b标明类型系统)
TS 采用的是结构化类型系统,也叫做 duck typing(鸭子类型),类型检查关注的是值所具有的形状
也就是说,在结构类型系统中,如果两个对象具有相同的形状,则认为它们属于同一类型
class Point { x: number; y: number }
class Point2D { x: number; y: number }
const p: Point = new Point2D()
解释:
在结构化类型系统中,如果两个对象具有相同的形状,则认为它们属于同一类型,这种说法并不准确
更准确的说法:对于对象类型来说,y的成员至少与 x 相同,则 x 兼容 y(成员多的可以赋值给少的)
class Point { x: number; y: number }
class Point2D { x: number; y: number; z: number }
const p: Point = new Point3D()
解释:
除了 class 之外,TS 中的其他类型也存在互相兼容的情况,包括:接口兼容性 函数兼容性
interface Point { x: number; y: number }
interface Point2D { x: number; y: number }
let p1: Point
let p2: Point2D = p1
interface Point3D { x: number; y: number; z: number }
let p3: Point3D
p2 = p3
class Point3D { x: number; y: number; z: number }
let p3: Point2D = new Point3D()
函数之间的兼容性比较复杂,需要考虑:参数个数、参数类型、返回值类型
参数个数,参数多的兼容参数少的(参数少的可以赋值给多的)
type F1 = (a: number) => void
type F2 = (a: number, b: number) => void
let f1: F1
let f2: F2 = F1
解释:
交叉类型(&):功能类似于接口继承(extends),用于组合多个类型为一个类型(常用于对象类型)
interface Person { name: string }
interface Contact { phone: string }
type PersonDetail = Person & Contact
let obj: PersonDetail = {
name: 'jack',
phone: '136...'
}
解释:使用交叉类型后,新的类型 PersonDetail 就同时具备了 Person 和 Contact 的所有属性类型。相当于
type PersonDetail = { name: string; phone: string }
交叉类型(&)和接口继承(extends)的对比:
# 接口继承方式
interface A {
fn: (value: number) => string
}
interface B extends A {
fn: (value: string) => string
}
# 直接报错,无法继承
# 交叉类型
interface A {
fn: (value: number) => string
}
interface B {
fn: (value: string) => string
}
type C = A & B
说明:以上代码,接口继承会报错(类型不兼容);交叉类型没有错误,可以简单的理解为:
fn: (value: string | number) => string
泛型是可以保证类型安全前提下,让函数等与多种类型一起工作,从而实现复用,常用于:函数、接口、class中
需求:创建一个 id 函数,传入什么数据就返回该数据本身(也就是说,参数和返回值类型相同)
function id(value: number): number { return value }
比如,id(10)调用以上函数就会直接返回 10 本身。但是,该函数只接收数值类型,无法用于其他类型
为了让函数能够接收任意类型,可以将参数类型修改为 any。但是,这样就失去了 TS 的类型保护,类型不安全
泛型在保证类型安全(不丢失类型信息)的同时,可以让函数等与多种不同的类型一起工作,灵活可复用。
创建泛型函数:
function id(value: Type): Type { return value }
解释:
调用泛型函数:
function id(value: Type): Type { return value }
const num: number
const num = id(10)
const num: string
const num = id('a')
解释:
简化调用泛型函数:
function id(value: Type): Type { return value }
let num: number
let num = id(10)
解释:
泛型约束:
默认情况下,泛型函数地类型变量 Type 可以代表多个类型,这导致无法访问任何属性
function id(value: Type): Type {
console.log(value.length)
return value
}
解释:Type 可以代表任意类型,无法保证一定存在length 属性,比如 number 类型就没有 length。此时就需要为泛型添加约束来收缩类型
指定更加具体的类型:
function id(value: Type[]): Type[] {
console.log(value.length)
return value
}
解释:将类型修改为 Type[](Type 类型的数组),因为只要是数组就一定存在 length 属性,因此就可以访问了
添加约束:
interface ILength { length: number }
function id(value: Type): Type {
console.log(value.length)
return value
}
解释:
注意:传入的实参只要有 length 属性即可,这也符合前面讲到的接口的类型兼容性
泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量约束)
function getProp(obj: Type, key: Key) {
return obj[key]
}
let person = { name: 'jack', age: 18 }
getProp(person, 'name')
解释:
泛型接口:
接口也可以配合泛型来使用,以增加其灵活性,增强其复用性
# 泛型接口
interface IdFunc {
id: (value: Type) => Type
ids: () => Type[]
}
let obj: IdFunc = {
id(value) { return value },
ids() { return [1, 3, 5] }
}
解释:
泛型类:
class 也可以配合泛型来使用。比如,React 的 class 组件的基类 Component 就是泛型类,不同的组件就有不同的 props 和 state
interface IState { count: number }
interface IProps { maxLength: number }
class InputCount extends React.Component {
state: IState = {
count: 0
}
render() {
return {this.props.maxLength}
}
}
解释:React.Component 泛型类两个类型变量,分别指定 props 和 state 类型
创建泛型类:
class GenericNumber {
defaultValue: NumType,
add: (x: NumType, y: NumType) => NumType
}
解释:
# 类似于泛型接口,在创建 class 实例时,在类名后面通过<类型>来指定明确的类型
TS 内置了一些常用的工具类型,来简化 TS 中的一些常见操作。
说明:它们都是基于泛型实现的(泛型适用于多种类型,更加通用),并且是内置的,可以直接在代码中使用
Partial
用来构造(创建)一个类型,将 Type 的所有属性设置为可选
interface Props {
id: string
children: number[]
}
type PartialProps = Partial
解释:构造出来的新类型PartialProps结构和 Props 相同,但所有属性都变为可选的
Readonly
用来构造(创建)一个类型,将 Type 的所有属性设置为readonly(只读)
interface Props {
id: string
children: number[]
}
type ReadonlyProps = Readonly
解释:构造出来的新类型ReadonlyProps结构和 Props 相同,但所有属性都变为只读的
Readonly
从 Type 中选择一组属性来构造新类型
interface Props {
id: string
children: number[]
}
type PickProps = Pick
解释:
Pick
构造一个对象类型,属性键为 Keys,属性类型为 Type
type RecordObj = Record<'a' | 'b' | 'c', string[]>
let obj: RecordObj = {
a: ['1'],
b: ['2'],
c: ['3']
}
解释:
绝大多数情况下,我们都可以在使用对象前就确定对象的结构,并为对象添加准确的类型。
使用场景:当无法确定对象中有哪些属性(或者说对象中可以出现任意多个属性),此时就用到索引签名类型了
interface AnyObject {
[key: string]: number
}
let obj:AnyObject = {
a: 1,
b: 2
}
解释:
基于旧类型创建新类型(对象类型),减少重复、提升开发效率
type PropKeys = 'x' | 'y' | 'z'
type newType = { [Key in PropKeys]: number }
解释:
映射类型除了根据联合类型创建新类型外,还可以根据对象类型来创建
type Props = { a: number; b: string; c: boolean }
type Type = { [key in keyof Props]: number }
解释:
泛型工具类型(比如,Partial
)是基于映射类型实现的
type Partial = {
[P in keyof T]?: T[P]
}
type Props = { a: number; b: string; c: boolean }
type PartialProps = Partial
解释:
T[P]
表示获取 T 中每个键对应的类型用到的T[P]
语法,在TS 中叫做索引查询(访问)类型
作用:用来查询属性的类型
type Props = { a: number; b: string; c: boolean }
type TypeA = number
type TypeA = Props['a']
解释:
索引查询类型的其他使用方式:同时查询多个索引的类型
type Props = { a: number; b: string; c: boolean }
type TypeA = Props['a' | 'b'];
# 使用字符串字面量的联合类型,获取属性 a 和 b 对应的类型,结果为:string | number
type TypeB = Props[keyof Props]
# 使用 keyof操作符获取 Props中所有键对应的类型,结果为: string | number | boolean
总结:.ts 是 implementation(代码实现文件); .d.ts 是 decalration(类型声明文件)