Typescript
Typescript 官网地址: https://www.typescriptlang.org/zh/
使用 nvm 来管理 node 版本: https://github.com/nvm-sh/nvm
装 Typescript:
npm install -g typescript
使用 tsc 全局命令:
// 查看 tsc 版本
tsc -v
// 编译 ts 文件
tsc fileName.ts
1.原始数据类型和Any类型
//布尔类型
let isDone: boolean = true
//数字类型
let age: number = 123
//字符串类型,模板字符串也可以
let firstName = 'Kiwi'
let message = `hello ${firstName}`
//undefined 和 null
let u: undefined = undefined
let n: null = null
//注:undefined 和 null 是所有类型的子类型
// let num: number = undefined
// 但是在新版的 ts 中,有一个特殊的配置是 strictNullChecks,
// 在有些编辑器中默认为 true。它会严格检查 null 以及 undefined 类型,它只能赋值给本身的类型或者 any。
//any:允许赋值为任意类型
let notSure: any = 4
notSure = 'maybe a string'
notSure = true
//在其上还可以任意调用方法和属性,在我们有明确类型时,应避免使用any
notSure.myName
notSure.geyName()
2.数组和元组
数组的声明方式如下,以纯数字数组举例
let arrayOfNumbers: number[] = [1, 2, 3]
arrayOfNumbers.push(3)
arrayOfNumbers.unshift(4)
//arrayOfNumbers.push('hello') 会报错
与js一样ts也可以使用一些数组的方法,但如果在方法里传入的参数与声明的类型不同的话,是会报错的
补充:我们都知道在js里有一种特殊的数据结构——类数组(array-like Objects),它很像数组,但是和数组不一样
//类数组
function test() {
console.log(arguments);
arguments.length
arguments[0]
// let arrat: any[] = arguments 不能把一个数组赋值给arguments
}
其具备数组的一些属性,如length,并且可以取其索引,但是它没有数组上的一些方法,并且我们不能把一个数组赋值给他,说明arguments和数组不是一个类型
其实在ts里给arguments专门定义了一个类型,叫IArguments
同理ts还有很多其他的内置类型,在这里不做细讲,后面如果有用到再会过来复习
元祖tuple:一定程度上限制了数据类型的数组
简单理解定义了具体索引的具体数据类型的数组
//元组
let user: [string, number] = ['kiwi', 21]
数据类型不对应或者数组内容多了或少了都会报错
但可以通过push()
方法突破这个限制,但是只能添加规定好的类型中的一种(上述例子即只能添加string和number中的一种)
user.push(123)
user.push('我饿了')
// user.push(true) 报错
3.Interface- 接口 初探
interface 对对象(shape)进行描述
IPerson 中I用于提示定义的是一个interface,我这里没加
readonly 和 const 的区别: readonly用于定义对象的属性只读,const 用于定义的变量只读
interface Person {
//readonly只读属性,只能在初始化时修改
readonly id: number
name: string
age: number
//可选属性 字面意思
sex?: string
}
let kiwi: Person = {
id: 1,
name: 'kiwi',
age: 21,
sex: '男'
}
kiwi.age = 23
// kiwi.id = 2 readonly属性不能二次修改
let mjy: Person = {
id:2,
name: 'mjy',
age: 21,
}
4.函数
// function add(x: number, y: number, z?: number): number {
// if (typeof z == 'number') {
// return x + y + z
// } else {
// return x + y
// }
// }
//函数表达式写法
const add = (x: number, y: number, z?: number): number => {
if (typeof z == 'number') {
return x + y + z
} else {
return x + y
}
}
//let add2:string=add赋值不适配赋值函数需要按照函数的方式进行生命
// 不是函数声明的情况下 给变量赋值表达式返回值需要使用箭头
let add2: (x: number, y: number, z?: number) => number = add
//interface在函数中的使用
interface ISum {
(x: number, y: number, z?: number): number
}
let add3: ISum = add
说实话有点没理解这个赋值函数类型的意义在哪里。。
这里当我们把一个变量赋值给一个函数的时候,这个变量就自动获得了一个类型,如下图所示,至于为什么下一节给大家解释
5.类型推论、联合类型和类型断言
上一节我们发现把一个变量赋值给一个函数的时候,这个变量就自动获得了一个类型,这其实是ts的一个原则——类型推论
类型推论:ts会自动推断当前变量的类型,如赋值无关类型则会报编译错误
例:
let num = 123
// num = '123'报错
之前我们学了any类型,any是一个大而全的数据类型,在不到万不得以的情况下我们不会使用它,但是现在假设有一种情况我们需要允许一部分的类型可以使用,比如说一个变量可以是字符串也可以是数字,其他类型则不行,那么这种情况我们该怎么处理呢
这里我们引入一个新的数据类型——联合类型(union types)
//union type
let numberOrStirng: number | string
numberOrStirng = 123
numberOrStirng = '123'
注意:联合类型的变量只能访问此联合类型所有类型里共有的属性或方法
,但有的时候我们又确实要在不确定变量类型的情况下访问对于的属性或方法
这里我们就要使用到类型断言
类型断言:将一种不确定的变量类型,告诉ts编译器,就是某一种变量类型。可欺骗编译器但避免不了运行时的报错(使用as关键字)
例:假设我们现在要一个getLength()函数,要求其输入的类型可以是number也可以是string,但返回的类型只能是number(因为获取的是长度)
function getLength(input: number | string): number {
const str = input as string
if (str.length) {
return str.length
} else {
const number = input as number
return number.toString().length
}
}
注意:类型断言不是类型转换,如果在这里定义一个变量将其断言为联合类型中不存在的数据类型的话,是会报错的
除了使用类型断言可以实现以外,我们还可以使用类型守卫type-guard
来实现(使用typeof关键字)
function getLength(input: number | string): number {
if (typeof input == 'string') {
return input.length //这里的input是string类型
} else {
return input.toString().length //这里的input自动变成了number类型
}
}
6.Class类
面向对象编程的三大特点
- 封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,
- 继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性。
- 多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。
class Animal {
name: string;
constructor(name: string) {
this.name = name
}
run() {
return `${this.name} is running`
}
}
const snake = new Animal('lily')
// 继承的特性
class Dog extends Animal {
bark() {
return `${this.name} is barking`
}
}
const xiaobao = new Dog('xiaobao')
console.log(xiaobao.run())
console.log(xiaobao.bark())
// 这里我们重写构造函数,注意在子类的构造函数中,必须使用 super 调用父类的方法,要不就会报错。
class Cat extends Animal {
constructor(name) {
super(name)
console.log(this.name)
}
run() {
return 'Meow, ' + super.run()
}
}
const maomao = new Cat('maomao')
console.log(maomao.run())
类成员的访问修饰符:
- public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
- private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
- protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
7.类与接口
用类实现一个接口
interface Radio {
switchRadio(trigger: boolean): void;
}
class Car implements Radio {
switchRadio(trigger) {
return 123
}
}
class Cellphone implements Radio {
switchRadio() {
}
}
interface Battery {
checkBatteryStatus(): void;
}
// 要实现多个接口,我们只需要中间用 逗号 隔开即可。
class Cellphone implements Radio, Battery {
switchRadio() {
}
checkBatteryStatus() {
}
}
8.enums枚举
enum Direction {
Up,
Down,
Left,
Right
}
console.log(Direction.Up); //0
// 还有一个神奇的点是这个枚举还做了反向映射
console.log(Direction[0]) //Up
enum Direction {
Up = 10,
Down,
Left,
Right
}
console.log(Direction.Down); //11
// 字符串枚举
enum Direction1 {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
}
const value = 'UP'
if (value === Direction1.Up) {
console.log('go up!')
}
//常量枚举constenum编译时直接编译成常量,提高性能
const enum Direction3 {
Up,
Down,
Left,
Right
}
9.泛型 Generics
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
function echo(arg) {
return arg
}
const result = echo(123)
// 这时候我们发现了一个问题,我们传入了数字,但是返回了 any
function echo<T>(arg: T): T {
return arg
}
const result = echo(123)
// 泛型也可以传入多个值
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]]
}
const result = swap(['string', 123])
10.约束泛型
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法
interface搭配泛型之后,可以灵活的返回不同的类型
创建一个拥有特定类型的容器,仿佛给一个容器贴标签一样
泛型就好像一个可变的参数,在用的时候传入,生成这个不同类型的一个容器,或者像上节课讲的用它来灵活的约束参数的类型,不需要参数是一个特别死板的类型,不希望他是一个特定string、number类型,我要传入的参数必须有某某属性、某某方法,否则就会报错。
在函数使用的时候,函数的这个类型推断,不会流入到函数体内,所以使用表达式,没法建立明确的绑定,用泛型可以让我们打破这个鸿沟,这个时候就可以返回它传入的类型。
class Queue {
private data = [];
push(item) {
return this.data.push(item)
}
pop() {
return this.data.shift()
}
}
const queue = new Queue()
queue.push(1)
queue.push('str')
console.log(queue.pop().toFixed())
console.log(queue.pop().toFixed())
//在上述代码中存在一个问题,它允许你向队列中添加任何类型的数据,当然,当数据被弹出队列时,也可以是任意类型。在上面的示例中,看起来人们可以向队列中添加string 类型的数据,但是那么在使用的过程中,就会出现我们无法捕捉到的错误,
class Queue<T> {
private data = [];
push(item: T) {
return this.data.push(item)
}
pop(): T {
return this.data.shift()
}
}
const queue = new Queue<number>()
//泛型和 interface
interface KeyPair<T, U> {
key: T;
value: U;
}
let kp1: KeyPair<number, string> = { key: 1, value: "str"}
let kp2: KeyPair<string, number> = { key: "str", value: 123}
11.类型别名、字面量、交叉类型
类型别名,其实对于js就是将一个表达式赋给一个新变量,目的就是为了,能减少重复代码量,
对于联合类型新的理解,就是或类型,意思就是可以是多个类型中的一个;也要加type关键字要不然和普通变量之间无法区别,自己尝试的时候忘加了type关键字,这个要注意
交叉类型:就是与的关系,但这个有点特殊它不是在选交集,它是多种类型同时满足的意思,用**关键字&**来连接;它可以配合接口来使用
//类型别名
//type-aliase
let sum: (x: number, y: number) => number
// const result = sum(1,2)
type PlusType = (x: number, y: number) => number
let sum2: PlusType
let result2 = sum2(1, 2)
type StrOrString = string | number
let res: StrOrString = 123
res = '123'
//字符串字面量
const str1: 'name' = 'name'
const number1: 1 = 1
type Direction = 'Up' | 'Down' | 'Left' | 'Right'
let toUp: Direction = 'Up'
//交叉类型
interface IName {
name: string
}
type person = IName & { age: number }
let me: person = { name: 'yjt', age: 21 }
12.声明文件
声明文件后缀:xxx.d.ts
它里面没有任何的实际实现代码,只有类型声明
可以用来查一些第三方库:https://types.kubajastrz.com/package/vue
声明文件一般是用于非ts编写的项目,即原来是js模块现在想用ts项目中在次使用
下面我们用一个小例子来体会一下
calculator.d.ts
//字面量
type IOperator = 'plus' | 'minus'
//传入两个参数,
//第一个看起来像是一个字符串,其实是一个限制了输入的字面量,限制只能输入 plus 或者 minus
//第二个输入的是一个数组,表示用于计算的数字
//type ICalculator = (operator: IOperator, numbers: number[]) => number
interface ICalculator {
(operator: IOperator, numbers: number[]): number //函数类型使用冒号
plus
}
declare const calculator: ICalculator
//模块导出
export default calculator
13.内置类型
const a: Array<number> = [1,2,3]
// 大家可以看到这个类型,不同的文件中有多处定义,但是它们都是 内部定义的一部分,然后根据不同的版本或者功能合并在了一起,一个interface 或者 类多次定义会合并在一起。这些文件一般都是以 lib 开头,以 d.ts 结尾,告诉大家,我是一个内置对象类型欧
const date: Date = new Date()
const reg = /abc/
// 我们还可以使用一些 build in object,内置对象,比如 Math 与其他全局对象不同的是,Math 不是一个构造器。Math 的所有属性与方法都是静态的。
Math.pow(2,2)
// DOM 和 BOM 标准对象
// document 对象,返回的是一个 HTMLElement
let body: HTMLElement = document.body
// document 上面的query 方法,返回的是一个 nodeList 类型
let allLis = document.querySelectorAll('li')
//当然添加事件也是很重要的一部分,document 上面有 addEventListener 方法,注意这个回调函数,因为类型推断,这里面的 e 事件对象也自动获得了类型,这里是个 mouseEvent 类型,因为点击是一个鼠标事件,现在我们可以方便的使用 e 上面的方法和属性。
document.addEventListener('click', (e) => {
e.preventDefault()
})
Typescript 还提供了一些功能性,帮助性的类型,这些类型,大家在 js 的世界是看不到的,这些类型叫做 utility types,提供一些简洁明快而且非常方便的功能。
// partial,它可以把传入的类型都变成可选
interface IPerson {
name: string
age: number
}
let viking: IPerson = { name: 'viking', age: 20 }
type IPartial = Partial<IPerson>
let viking2: IPartial = { }
// Omit,它返回的类型可以忽略传入类型的某个属性
type IOmit = Omit<IPerson, 'name'>
let viking3: IOmit = { age: 20 }
14.配置文件 tsconfig.json
官方文档:https://www.typescriptlang.org/tsconfig
- files:运行tsc时会编译括号内的文件
- compilerOptions:怎么编译?
- outDir:编译好的文件应该输出放到哪里,默认是当前文件夹
- module:指要输出的module的类型
- target:选择要符合的ES标准
- declaration:true自动生成目录下文件的声明文件
{
"files": ["test.ts", "test2.d.ts"],
"compilerOptions": {
"outDir": "./output",
"module": "ESNext",
"target":"ES5",
"declaration": true
}