TypeScript 认识——入门篇 | 青训营笔记

330 阅读13分钟

TypeScript 入门

前言

这是我参与「第四届青训营」笔记创作活动的的第5天

让我们一起了解认识TypeScript到底有何不同,学习它的一些类型如何使用

TypeScript = JavaScript + 类型系统

What is TypeScript

关于什么是TypeScript,这里就不过多介绍,详情请点击这里

TypeScript 类型

常用基础类型

划分

可以将TS的常用基础类型细分为两类:

  1. JS已有类型
  • 原始类型(基础数据类型):number/string/boolean/null/undefined/symbol
  • 对象类型:object
  1. TS新增类型
  • 联合类型,自定义类型(类型别名),接口,元组,字面量类型,枚举,void,any等

原始类型

原始类型特点: 简单,完全按照JS中的类型来书写。

image.png

对象类型

特点:对象类型在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中叫联合类型(由两个或多个其他类型组成的类型,表示可以是这些类型中的任意一种)

类型别名

类型别名(自定义类型):为任意类型奇别名

使用场景:当同一类型被多次使用时,可以通过类型别名,简化类型使用。 image.png

其他

type关键字创建类型别名

image.png

函数类型

两种写法(有返回值):

  1. 单独指定参数,返回值类型:
// 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
  1. 同时指定参数,返回值类型
// 第一第二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('大佬')

函数参数可传可不传

image.png

对象类型

js中的对象是否属性和方法构成,TS中的对象类型就是在描述对象的结构(有什么类型的属性和方法)

写法

let person: {name: string; age: number; sayHi(): void; greet(name: number):void} = {
  name: '大佬好',
  age: 18,
  sayHi() {},
  greet(name) {}
}

image.png

可选属性

对象的属性或方法,可选可不选,就可以用可选属性(用?表示可选),如:

function myAxios(config: {url: string; method?: string}){}
myAxios({
  url: ''
  //
  method可不填
})

_______________________________________________________

下面介绍TS新增的类型(js本来就没有的)

接口 interface

接口使用

接口(interface):当一个对象类型被多次使用时,一般会使用接口来描述对象的类型,从而达到复用的目的。

解释:

  1. 使用interface关键字声明接口
  2. 接口名称可以是任意合法的变量名称(eg: IPerson)
  3. 声明接口后,直接使用接口名称作为变量的类型
  4. 因为每一行只有一个属性类型,因而属性类型后没有(分号)
// 接口
interface IPerson {
  name: string
  age: number
  sayHi(): void
} 

let person1: IPerson = {
   name: '大佬',
   age: 16,
   sayHi(){}
}

let person2: IPerson = {
  name: '大佬',
  age: 1,
  sayHi() {}
}

接口 VS 类型别名

  • 相同点:都可以给对象指定类型
  • 不同点:
    • 接口只能为对象指定类型
    • 类型别名可以为任意类型指定别名

image.png

接口继承 extends

如果两个接口之间有相同的属性或方法,就可以将公共的属性或方法剥离出来,通过继承来实现复用。 eg:

interface Node1 {x: numbery: string}
interface Node2 {x: numbery: string, z: number}

上面代码有重复的,很繁琐,可以有关键字extends继承实现接口Node2继承Node1:

interface Node2 extends Node1 {z: number}

用extends继承公共的属性或方法后,是不是觉得上段代码恒简洁,恒舒服!!!

元组

元组(Tuple)类型是另一种类型的数组,它确切的知道包含多少个元素,以及特定索引对应的类型。

eg:

image.png

类型推论

在TS中,某些没有明确指出类型的地方,TS的类型推论机制就会提供帮助。换句话说:由于类型推论的存在,有些情况下,类型注解可以省略不写—偷懒

发生类型推论的2种场景:

  1. 声明变量并初始化时
  2. 决定函数返回值时

image.png

类型断言 as

有些时候你会比TS更加想明确一个值得类型,因而可以用类型断言来指定更具体的类型。

eg:

image.png

<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')

image.png other egs

image.png

字面量类型

来个栗子

// 思考一下,以下两个变量类型分别为?
let str1 = 'hello dalao'

const str2 = 'hello dalao'

有人秒解答:都是string类型!!!

然而并非如此

正解:

通过类型推论机制就可以得到答案(鼠标放到str1,str2就会显示类型)

  1. str1是一个变量(let),其值可以是任意字符串,所以类型为string
  2. 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'
}
// 字符串枚举没有自增行为,因此字符串枚举的每个成员都必须有初始值

解释

  1. 使用enum关键字定义枚举
  2. 约定枚举类型名称,枚举中的值以大写字母开头
  3. 枚举中的多个值之间通过分隔开
  4. 定义好枚举后,直接使用枚举名称作为类型注解
  5. 访问枚举成员可用点(.)的形式

特殊性

  • 枚举是TS的非JavaScript类型级扩展(不仅仅是类型)的特性之一

  • 原因:其他类型仅仅被当作类型,而枚举不仅用作类型,还提供值(枚举类型都是有值的)

  • 其他类型在编译为JS代码是自动移除,但是枚举类型的类型成员会作为对象的属性,而枚举成员的值会作为属性的值

枚举类型编译 image.png

any 类型

当值为any是,可以进行任意操作,失去TS类型保护优势, 所以不推荐使用any

let fruit: any = {name: '香蕉'}
person.color = 'yellow'
fruit()
const shuiguo: string = fruit
// 以上操作都不会有任何的类型错误提示,即使存在错误!!!

注意

  • 尽可能的避免使用any类型,除非临时使用any来避免书写很长,复渣的类型(代码写到一半)

  • 其他隐式具有any类型情况:

    1. 声明变量不提供类型也不提供默认值
    2. 函数参数不加类型
// 隐式具有`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}){}

解释

  1. 使用typeof操作符获取 getnum 的类型,结果和第一种相同

  2. typeof只能用来查询变量或属性的类型,无法查询其他形式的类型(如,函数调用的类型)

常用高级类型

划分

TS高级类型较多,重点介绍一下类型

高级类型

  1. class类
  2. 类型兼容性
  3. 交叉类型
  4. 泛型,keyof
  5. 索引签名,索引查询类型
  6. 映射类型

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)

类继承

  1. extends(继承父类),js中只有extends

image.png

  1. implements (实现接口),TS提供的implements

咱们来看看implements与extends相比有何不同:

image.png

implements关键字可以实现 Person类 继承 Singer的所有属性及方法,但是现在提示错误,原来啊,接口Singer里有的属性和方法都要写出来(写在Person类中),如下图:这样就不会提示错误了!

image.png

类成员可见性

TS提供了许多可见性修饰符,从而控制class的方法和属性对于class外的代码是否可见

可见性修饰符:

  1. public:表示公有的,可以被任意地方访问,类成员默认为 public
class Animal{
  // 任何地方可访问
  // 也可不写,默认为public
  public say() {
    console.log('汪汪')
  }
}
  1. protected:表示受保护的,仅对其声明所在的类和子类(非实例对象)中可见。简单来说就是实例对象不可访问该属性或方法

image.png 可以看到实例对象dog只能访问say和walk方法,而不能访问run方法(run受到保护)

  1. private:表示私有的,只在当前类中可见,对其实例对象及子类都不可见。简单来讲就是子类继承父类后访问不了父类中private的属性或方法
class Animal {
  // private 私有方法只能在内部(Animal类中)访问
  private sleep(){
    console.log('睡觉');
  }
  walk() {
    this.sleep()
    console.log('走了');
  }
  protected run() {
    this.sleep()
    console.log('跑啊');
  }
}
  1. readonly:表示只读,用来防止在构造函数之外对属性进行赋值(该修饰符不能修饰方法)
class Person{
  // 只读属性
  // 只要是readonly来修饰的属性,必须手动提供明确的类型
  readonly age: number = 18
  
  constructor(age: number) {
    // age 只能在构造函数中赋值
    this.age = age
  }
}

注意:接口或者{}表示的对象类型,也可使用readonly

image.png

类型兼容性

  • 结构化类型兼容系统(Structural Type System)
  • 标明类型系统(Nominal Type System)

Ts采用的是结构化类型兼容系统,也叫做duck typing(鸭子类型),类型检查关注的是值所具有地形状。

image.png

class类兼容性

在结构化类型系统中,对于对象类型来说,y成员至少与x的相同,则x兼容y(成员多的可以赋值给少的)

class Point { x: number; y: number}
class Point3D {x: number; y: number; z: number}
const p: Point = new Point3D()

解释:

  1. Point3D的成员字少与Point相同,则Point兼容Point3D
  2. 成员多的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

函数兼容性

  1. 参数个数: 参数多的兼容参数少的(参数少的可以赋值给参数多的)
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)=>{})

image.png

  1. 参数类型:相同位置的参数类型要相同后兼容
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 // 提示错误

image.png

  1. 返回值类型

![image.png](p3-juejin.byteimg.com/tos-cn-i-k3…. image?)

交叉类型

功能类似于接口继承(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中。

image.png 泛型在保护类型安全(不丢失类型信息的)同时,可以让函数与多种不同类型一起工作,灵活可复用。 实际上,在C#和Java中,泛型都是用来实现可复用组件功能的主要工具之一。

// 创建泛型
function id<Type>(value: Type): Type{return value}

image.png

image.png

由于TS有一个类型参数推断的机制。可以根据传入的实参推断类型变量Type的类型。所以可以简化泛型函数调用:

function id<Type>(value: Type): Type{return value}
let num = id(100)

注:当然,当编译器无法推断类型或者推断的类型不准确时,就需要显示传入类型参数(原本该怎么写就怎么写)

泛型约束

默认情况下,泛型函数的类型变量Type可以代表多个类型,这导致无法访问任何属性,此时就需要为泛型添加约束来缩窄类型取值范围 image.png 添加约束

  1. 指定更具体的类型
// 将类型修改为Type
function id<Type>(value: Type[]): Type[]{
  console.log(value.length) // 这样就不会报错了
  return value
}
  1. 添加约束

image.png

image.png

注意:传入的实参(比如数组)只要有length属性就行。

总结

写惯了JS,刚开始写TS会感觉非常难受。慢慢来吧,不经历风雨怎能见彩虹!关注我,本文会持续更新哦!未完待续~~~