TypeScript 入门
前言
这是我参与「第四届青训营」笔记创作活动的的第5天
让我们一起了解认识TypeScript到底有何不同,学习它的一些类型如何使用
TypeScript = JavaScript + 类型系统
What is TypeScript
关于什么是TypeScript,这里就不过多介绍,详情请点击这里
TypeScript 类型
常用基础类型
划分
可以将TS的常用基础类型细分为两类:
- JS已有类型
- 原始类型(基础数据类型):number/string/boolean/null/undefined/symbol
- 对象类型:object
- TS新增类型
- 联合类型,自定义类型(类型别名),接口,元组,字面量类型,枚举,void,any等
原始类型
原始类型特点: 简单,完全按照JS中的类型来书写。
对象类型
特点:对象类型在TS更加细化,对每个的对象都有自己的类型语言。
数组类型
两种写法:
// 1 推荐写法
const numbers: number[] = [1,2,3]
// 2
const strings: Array<string> = ['a', 'b', 'c']
如果是数组中既有number,又有string,该怎么写?
联合类型
const arr: (number | string)[] = [1,'a',2,'b']
注:|(竖线)在TS中叫联合类型(由两个或多个其他类型组成的类型,表示可以是这些类型中的任意一种)
类型别名
类型别名(自定义类型):为任意类型奇别名
使用场景:当同一类型被多次使用时,可以通过类型别名,简化类型使用。
其他
type关键字创建类型别名
函数类型
两种写法(有返回值):
- 单独指定参数,返回值类型:
// 1.
// 第一第二number分别为参数num1,num2添加数字类型,第三个number则为返回值添加数字类型
function plus(num1: number, num2: number): number {
return num 1+ num2
}
plus(1,2) // 3
// 2.
// 同上
const add = (num1: number, num2: number): number => {
return num1 + num2
}
add(2,3) // 5
- 同时指定参数,返回值类型
// 第一第二number分别为参数num1,num2添加数字类型,第三个number则为返回值添加数字类型
const add: (num1: number, num2: number) => number = (num1, num2) => {
return num1 + num2
}
add(2,3) // 5
注:这种形式(第二种)只适合用于函数表达式
void(无返回值):
// void类型
function greet(name: string): void {
console.log('hello',name)
}
greet('大佬')
函数参数可传可不传:
对象类型
js中的对象是否属性和方法构成,TS中的对象类型就是在描述对象的结构(有什么类型的属性和方法)
写法
let person: {name: string; age: number; sayHi(): void; greet(name: number):void} = {
name: '大佬好',
age: 18,
sayHi() {},
greet(name) {}
}
可选属性
对象的属性或方法,可选可不选,就可以用可选属性(用?表示可选),如:
function myAxios(config: {url: string; method?: string}){}
myAxios({
url: ''
//
method可不填
})
_______________________________________________________
下面介绍TS新增的类型(js本来就没有的)
接口 interface
接口使用
接口(interface):当一个对象类型被多次使用时,一般会使用接口来描述对象的类型,从而达到复用的目的。
解释:
- 使用
interface关键字声明接口 - 接口名称可以是任意合法的变量名称(eg: IPerson)
- 声明接口后,直接使用接口名称作为变量的类型
- 因为每一行只有一个属性类型,因而属性类型后没有
;(分号)
// 接口
interface IPerson {
name: string
age: number
sayHi(): void
}
let person1: IPerson = {
name: '大佬',
age: 16,
sayHi(){}
}
let person2: IPerson = {
name: '大佬',
age: 1,
sayHi() {}
}
接口 VS 类型别名
- 相同点:都可以给对象指定类型
- 不同点:
- 接口只能为对象指定类型
- 类型别名可以为任意类型指定别名
接口继承 extends
如果两个接口之间有相同的属性或方法,就可以将公共的属性或方法剥离出来,通过继承来实现复用。 eg:
interface Node1 {x: number,y: string}
interface Node2 {x: number,y: string, z: number}
上面代码有重复的,很繁琐,可以有关键字extends继承实现接口Node2继承Node1:
interface Node2 extends Node1 {z: number}
用extends继承公共的属性或方法后,是不是觉得上段代码恒简洁,恒舒服!!!
元组
元组(Tuple)类型是另一种类型的数组,它确切的知道包含多少个元素,以及特定索引对应的类型。
eg:
类型推论
在TS中,某些没有明确指出类型的地方,TS的类型推论机制就会提供帮助。换句话说:由于类型推论的存在,有些情况下,类型注解可以省略不写—偷懒
发生类型推论的2种场景:
- 声明变量并初始化时
- 决定函数返回值时
类型断言 as
有些时候你会比TS更加想明确一个值得类型,因而可以用类型断言来指定更具体的类型。
eg:
<a href="http://www.jd.com" id="link">京东</a>
const aLink = document.getElementById('link')
aLink.href
// 此时访问不到href属性而提示报错,因为类型太过宽泛(不具体),无法操作href等a标签特有的属性和方法。
// 解决:使用类型断言
// 1.推荐使用
const aLink = document.getElementById('link') as HTMLAnchorElement
aLink.href
// 2.不常用
const aLink = <HTMLAnchorElement>document.getElementById('link')
other egs
字面量类型
来个栗子:
// 思考一下,以下两个变量类型分别为?
let str1 = 'hello dalao'
const str2 = 'hello dalao'
有人秒解答:都是string类型!!!
然而并非如此
正解:
通过类型推论机制就可以得到答案(鼠标放到str1,str2就会显示类型)
- str1是一个变量(let),其值可以是任意字符串,所以类型为
string - str2是一个常量(const),它的值不是变化的只能是 ‘hello dalao’ ,所以类型为
'hello dalao'
注:这里的'hello dalao'就是一个字面量类型。也就是说某个特定的字符串也可以作为TS的类型。除字符串外,任意js字面量(数字,对象)都可以作为类型使用!
使用模式:一般情况下,字面量类型配合联合类型一起使用
使用场景:用来表示一组的可选值列表
eg:在贪吃蛇游戏里方向选择、只有上下左右任意一个
// direction 的值只能从四个值之中选
function changeDirection(direction: 'up' | 'down' | 'left' | 'right') {
console.log(direction)
}
changeDirection('left') // left
优势:相比于string类型,字面量类型更加精准
枚举类型 enum
枚举:定义一组常量。它描述一个值,这个值可以是这些命名常量中的一个。枚举类型的功能类似于字面量类型+联合类型组合的功能。比如上述代码中的direction功能类似于枚举类型的功能
eg:
enum Direction {Up, Down, Left, Right}
function changeDirection(direction: Direction){
console.log(direction)
}
changeDirection(Direction.Left) // 2
changeDirection(Direction.Up) // 0
注意:
- 形参
direction类型为枚举Direction - 枚举成员是有值的,默认为:从0开始自增的数字(针对数字枚举)
- 枚举成员可进行初始化值
//
enum Direction {
Up, // 默认0,以后自增+1
Down, // 1
Left, // 2
Right // 3
}
//
enum Direction {
Up=10, // 10,以后自增+1
Down, // 11
Left, // 12
Right // 13
}
// 枚举成员可进行初始化值
enum Direction {
Up ='',
Down = '12',
Left= '1123',
Right= '234'
}
// 字符串枚举没有自增行为,因此字符串枚举的每个成员都必须有初始值
解释:
- 使用
enum关键字定义枚举 - 约定枚举类型名称,枚举中的值以大写字母开头
- 枚举中的多个值之间通过
,分隔开 - 定义好枚举后,直接使用枚举名称作为类型注解
- 访问枚举成员可用点(.)的形式
特殊性
-
枚举是TS的非JavaScript类型级扩展(不仅仅是类型)的特性之一
-
原因:其他类型仅仅被当作类型,而枚举不仅用作类型,还提供值(枚举类型都是有值的)
-
其他类型在编译为JS代码是自动移除,但是枚举类型的类型成员会作为对象的属性,而枚举成员的值会作为属性的值
枚举类型编译
any 类型
当值为any是,可以进行任意操作,失去TS类型保护优势,
所以不推荐使用any
let fruit: any = {name: '香蕉'}
person.color = 'yellow'
fruit()
const shuiguo: string = fruit
// 以上操作都不会有任何的类型错误提示,即使存在错误!!!
注意:
-
尽可能的避免使用any类型,除非临时使用
any来避免书写很长,复渣的类型(代码写到一半) -
其他隐式具有
any类型情况:- 声明变量不提供类型也不提供默认值
- 函数参数不加类型
// 隐式具有`any`类型
// 1.
let a
// 可以进行任意操作
a = 1
a()
// 2.
function add(num1,num2){}
// 可以进行任意操作
add(1,2)
add(1,'2')
add(2,false)
typeof 操作符
typeof:可以在类型上下文中引用变量或属性的类型(类型查询)
使用场景:根据已有的变量的值,获取该值的类型,简化类型书写
let getnum = {x: 1, y: 2}
function formatNum(num: typeof getnum){}
formatNum(getnum)
// 运用 typeof 改进代码书写
function formatNum(num: {x: number; y: number}){}
解释:
-
使用
typeof操作符获取 getnum 的类型,结果和第一种相同 -
typeof只能用来查询变量或属性的类型,无法查询其他形式的类型(如,函数调用的类型)
常用高级类型
划分
TS高级类型较多,重点介绍一下类型
高级类型:
- class类
- 类型兼容性
- 交叉类型
- 泛型,keyof
- 索引签名,索引查询类型
- 映射类型
class类
基本用法
- class定义方法的类型注解
class Person{
age: number
gender: string
constructor(age: number,gender: string) {
this.age = age
this.gender = gender
}
}
const per = new Person(18,'男')
console.log(per.age,per.name) // 18 男
- class定义方法的类型注解
class Point{
x=10
y=10
mult(n: number): void {
this.x *= n
this.y *= n
}
}
const point = new Point()
point.mult(10)
console.log(point.x, point.y)
类继承
extends(继承父类),js中只有extends
implements(实现接口),TS提供的implements
咱们来看看implements与extends相比有何不同:
用implements关键字可以实现 Person类 继承 Singer的所有属性及方法,但是现在提示错误,原来啊,接口Singer里有的属性和方法都要写出来(写在Person类中),如下图:这样就不会提示错误了!
类成员可见性
TS提供了许多可见性修饰符,从而控制class的方法和属性对于class外的代码是否可见
可见性修饰符:
public:表示公有的,可以被任意地方访问,类成员默认为 public
class Animal{
// 任何地方可访问
// 也可不写,默认为public
public say() {
console.log('汪汪')
}
}
protected:表示受保护的,仅对其声明所在的类和子类(非实例对象)中可见。简单来说就是实例对象不可访问该属性或方法
可以看到实例对象dog只能访问say和walk方法,而不能访问run方法(run受到保护)
private:表示私有的,只在当前类中可见,对其实例对象及子类都不可见。简单来讲就是子类继承父类后访问不了父类中private的属性或方法
class Animal {
// private 私有方法只能在内部(Animal类中)访问
private sleep(){
console.log('睡觉');
}
walk() {
this.sleep()
console.log('走了');
}
protected run() {
this.sleep()
console.log('跑啊');
}
}
readonly:表示只读,用来防止在构造函数之外对属性进行赋值(该修饰符不能修饰方法)
class Person{
// 只读属性
// 只要是readonly来修饰的属性,必须手动提供明确的类型
readonly age: number = 18
constructor(age: number) {
// age 只能在构造函数中赋值
this.age = age
}
}
注意:接口或者{}表示的对象类型,也可使用readonly
类型兼容性
- 结构化类型兼容系统(Structural Type System)
- 标明类型系统(Nominal Type System)
Ts采用的是结构化类型兼容系统,也叫做duck typing(鸭子类型),类型检查关注的是值所具有地形状。
class类兼容性
在结构化类型系统中,对于对象类型来说,y成员至少与x的相同,则x兼容y(成员多的可以赋值给少的)
class Point { x: number; y: number}
class Point3D {x: number; y: number; z: number}
const p: Point = new Point3D()
解释:
- Point3D的成员字少与Point相同,则Point兼容Point3D
- 成员多的Point3D可以赋值给少的Point
接口兼容性
情况与上述class类兼容性类似,这里看个栗子:
interface Point { x: number; y: number}
interface Point2D { x: number; y: number}
let p1: Point
let p2: Point2D = 1
interface Point3D {x: number; y: number; z: number }
let p3: Point3D
p2 = p3
函数兼容性
- 参数个数: 参数多的兼容参数少的(参数少的可以赋值给参数多的)
type F1 = (a: number) => void
type F2 = (a: number, b: number) => void
let f1: F1
let f2: F2 = f1
//
const arr = ['a','b','c']
arr.forEach(()=>{})
arr.forEach((item)=>{})
- 参数类型:相同位置的参数类型要相同后兼容
interface Point2D { x: number; y: number}
interface Point3D { x: number; y: number; z: number}
type F2 = (p: point2D) => void
type F3 = (p: point3D) => void
let f2: F2
lwt f3: F3 = f2
f2 = f3 // 提示错误
- 返回值类型

交叉类型
功能类似于接口继承(extends),用于组合多个类型为一个类型(常用于对象类型)
看代码:
interface Person { name: string }
interface Contact { phone: string }
// 交叉类型
type PersonDetail = Person & Contact
// 相当于 type PersonDetail = { name: 'jack', phone: '134...'}
let obj: PersonDetail = {
name: 'jack',
phone: '134...'
}
解释:使用交叉类型后,类型PersonDetail就具有了Person和Contact的所有属性类型
交叉类型 VS 接口继承
- 相同点:都可实现对象类型的组合
- 不同点:实现类型组合时,对于同名属性之间,处理冲突方式不同
interface A {
fn: (value: number)=> string
}
// 接口继承
interface B extends A
fn: (value: number)=> string
}
// 此时B会提示错误,因为类型不兼容
interface C{
fn: (value: string) => string
}
// 交叉类型
type C = A & C
// 此时不会报错,可理解为: fn: (value: number | string) => string
泛型
泛型是可以载保证类型安全的前提下,让函数与多种类型一起工作,从而实现复用,常用于:函数,接口,class中。
泛型在保护类型安全(不丢失类型信息的)同时,可以让函数与多种不同类型一起工作,灵活可复用。
实际上,在C#和Java中,泛型都是用来实现可复用组件功能的主要工具之一。
// 创建泛型
function id<Type>(value: Type): Type{return value}
由于TS有一个类型参数推断的机制。可以根据传入的实参推断类型变量Type的类型。所以可以简化泛型函数调用:
function id<Type>(value: Type): Type{return value}
let num = id(100)
注:当然,当编译器无法推断类型或者推断的类型不准确时,就需要显示传入类型参数(原本该怎么写就怎么写)
泛型约束
默认情况下,泛型函数的类型变量Type可以代表多个类型,这导致无法访问任何属性,此时就需要为泛型添加约束来缩窄类型取值范围
添加约束:
- 指定更具体的类型
// 将类型修改为Type
function id<Type>(value: Type[]): Type[]{
console.log(value.length) // 这样就不会报错了
return value
}
- 添加约束
注意:传入的实参(比如数组)只要有length属性就行。
总结
写惯了JS,刚开始写TS会感觉非常难受。慢慢来吧,不经历风雨怎能见彩虹!关注我,本文会持续更新哦!未完待续~~~