这是一篇带你快速入门的TS教程

574 阅读11分钟

TypeScript

TS 中的基础类型

TS 中的常用基础类型分为两种,第一种是已有类型,第二种是新增类型

JS已有类型

原始类型

原始类型有: number/string/boolean/undefined/null/symbol

这些类型可以直接使用类型注解书写

let n:number = 1

let s:string = '123'

let b:boolean = false

let n:null = null
 
let u:undefined = undefined

let s:symbol = Symbol()

类型推论

在 TS 中, 某些使用变量的时候 TS 会自动的添加类型注解,可以将其省略不些

存在有类型推论的场景

  1. 初始化变量
  2. 函数的返回值

初始化变量

const age = 18 // 自动的补全为 number 类型

函数的返回值

function sum(num1:number,num2:number) {
    return num1 + num2
}

新增类型

联合类型

让一个变量的值既可以是 null 又可以是 number

const 变量名 :( 类型1 | 类型2 ) = 类型1 的值 | 类型2 的值

const num:( string | number ) = 1 | '1'

| 叫做联合符,可以由两个及其以上的类型联合约束变量的值,表示可以为约束中的任意一种类型

交叉类型

让两个类型融合在一起,形成一个新的类型

interface type1 {
    name:string
}

type type2 = type1 & { age:number }

const obj:type2 = {
    name:'ikun',
    age:19
}

类型别名

对于一些需要重新命名注解类型的需求,可以使用类型别名来更改其类型

type s = string

const myname:s = 'ikun'

对于多种联合类型重复利用的时候可以使用类型别名

如:

type asNumber = string | number | null | undefined

const age:asNumber = 1

数组类型

数组类型的语法有两种注解方式

直接书写

const 数组名 : 类型[] = [数组元素,数组元素,数组元素。。。]

const arr : (string | number)[] = ['123',1,2,3]

使用构造函数式

const 数组名 : Array<类型> = [数组元素,数组元素,数组元素...]

const arr:Array<( string | number )> = ['1','2','3',4,5]

函数

函数的约束实际上就是指的是参数和返回值的约束

参数
function getSum (num1:numer,num2:numer) {}

const getSum  = (num1:numer,num2:number) => {}
返回值
function getSum (num1:number,num2:number):number {
    return num1 + num2
}

const getSum = (num1:number,num2:number) :number => {
    return num1 + num2
}

统一设置类型注解

利用 type 关键词存储相关类型

type getSum = (num1:number,num2:number) => number

const  getSum:getSum = (num1,num2) => num1 + num2

注意: 该方法只适用于箭头函数

函数的返回 void

函数的返回值如果没有 return ,类型应该定义为 void

一般有三种情况

  1. 函数没有 return
  2. 函数 return 没有后续内容
  3. 函数 return undefined

注意:函数没有返回值可以声明类型为 void ,但是不能直接声明为 undefined

可选参数

函数参数类型注解中的参数一定要传入,否则就会报错,如果某些参数不是必传项,可以使用可选参数

function getSum(num1:number,num2:numer,num3?number) {
   if(num3)  return num1 + num2 + num3 ? num3 : 0
    else  return num1 + num2 
}

可选参数不能直接拿来使用,因为可能是 undefined 而报错

默认值

函数参数的默认值在参数不传的情况下可以设置

function getSum (num1:number.num2:number,num3:number = 10) {
    return num1 + num2 + num3
}

默认值不可以和可选参数一起使用,因为默认值的优先级比可选参数优先级更高,混在一起使用没有任何意义

函数重载

函数重载是一种利用相同的函数名,传入不同的参数类型或者不同数量的参数返回不同的类型或者值的一种方式

function getSum(a:number,b:number):number
function getSum(a:string,b:number):string
function getSum(a:number,b:string):string
function getSum(a:string,b:string):string
function getSum(a:number,b:number,c:number):number
function getSum(a:number,b:number,c:string):sring
function getSum(a:any,b:any,c:any):number | string {
    return a + b + ( c ? c : 0 )
}

可以理解为是一种函数类型和参数的模式,根据参数的类型,参数的数量,函数的返回值来匹配对应的函数,获取相关的返回值,最后一个应该为 any 类型,如果类型都没有匹配上,则会使用最后一个函数

对象

对象的类型注解需要单独配置,定义类型别名 或者是使用接口

单独配置

const obj :{
    name:string,
    age:number,
    sing:(str?:string)=>void
} = {
    name:'ikun',
    age:18,
    sing(str?:string) {
        console.log(str)
    }
}

使用类型别名

type useObj = {
    name:string,
    age:number,
    sing:(str:string)=>void
}

接口

处理类型被多个对象注解的方式之一,另一个是类型别名,以达到类型复用的目的

interface Iobj {
    name:string
    age:number
    sing:(str:string) => void
}

const obj:Iobj = {
    name:'kun',
    age:18,
    sing:(str:string) => {}
}
接口和类型别名的区别
  1. 接口可以被继承 extends
  2. 接口只能服务于对象
  3. 类型别名可以服务于任意类型
  4. 能使用 type 类型别名就使用类性别名

继承

使用类继承符 extends 即可实现继承

interface 接口类型1 :{
    name:string
    age:number
    sing:(str:stirng)=>void
};

interface 接口类型2 extends 接口类型1 {
	属性:类型 // 接口类型2 的静态类型
    属性: 类型
}

使用继承接口后,子接口可以使用父接口的所有类型注解,并且子接口中还可以自由定义子接口的相关接口

只读类型

type 类型名 = {
    属性名1:类型,
    readonly 属性名2:类型
}

或者 

interface 接口名 {
    属性名1:类型,
    readonly 属性名2:类型
}

多次定义

接口可以被定义多次,多次定义下的接口将会被合并为一个接口

interface Ipoint = { x:number }
interface Ipoint = { y:number }

const obj:Ipoint = { x:1,y:2 }

元组 (Tuple)

元组是一个特殊的数组类型注解,他约定了数组的长度和数组对应下标的类型

如:

const example:[number,string] = ['123',123]

实际应用:hooks 中的 useState 就是返回了一个元组类型的数据

const useState = (num:number):[number,(num:number)=>void]=>{
	const setNum = (num1:number)=>{
		num = num1
	}
	return [num,setNum]
}

字面量类型

字面量类型一般都约束一个变量的数据只能为类型字面量,可以将其理解为 const 关键字

如:

let str:'nihao' = 'nihao'

let num:123= 123

只要数据不于类型相匹配就会报错

她可以配合联合类型使用,来约束一个变量的值只能为联合类型中的一个

let str:('nihao' | 'hallo') = 'nihao'

任意字面量都可以作为类型,如果作为类型使用一般用于取到某个特定的值

枚举

枚举能够通过关键字约束数据的值为枚举类型中的一个,也和 字面量 + 联合类型 功能类似

let num1 = 1
let num1 = 2
let num1 = 3

enum Enum {num1,num2,num3}

let num:enum = Enum.num1

枚举集合里面不允许添加数值

使用方法时

enum direct { Up,Down,Left,Right }

function getDirect(directive:direct) {
    console.log(directive)
}

getDirect(direct.up) // 如果没有设置值,默认值从 0 开始依次递增

枚举的值在未定义的情况下是从 0 开始的,逐个递增,也可以直接对其赋值

enum direct { Up=123,Down:'123',Left:456,right:'456' }

let num:direct = 123 || '123' || 456 || '456'

function getDirect(directive:direct):void {
    console.log(directive)
}

getDirect(direct.Up)

注意:字符串的枚举不会自增,枚举的每个成员都必须要有初始值

场景:利用枚举实现性别 (0,1) 标识

enum GENDER {
    boy,
    girl
}

interface Igender {
    name:string
    gender:GENDER
}

const obj : Igender = {
    name:'tom',
    gender: GENDER.boy // 实际会显示 0 / 1
}

any

当数据类型为 any 的时候就取消了 type 限制,变量中可以存储任意类型的数据,所以 any 也是 TS 中的顶级类型

let obj:any = 1

obj = '1'

obj = null 

obj = undefined 

obj = true

obj = Symbol()

注意:如果类型为 any ,编译式或者书写时不会出现错误,但是运行中可能因为 TSlint 中的报错而导致程序意外终止

断言

断言就是当开发者明确值的类型,并手动约束值类型的操作

语法为

const 变量 = 值 as 类型

或者

const 变量 = (<类型>  值)

如果值与预期的类型不符合,就会报错 ( unknow 类型除外 )

对于预期的值,并且需要获取到预期值的某个属性的时候,可以使用断言

type User = {
    name:string,
    age:18
}
const user = {} as User 
console.log(user.name) // 这里如果不使用断言会报错,因为 user 上不存在一个名为 name 的属性

typeof

当不知道一个数据的类型,并且需要将其作为依赖操作的时候,可 以使用 typeof 操作

const res = { name: '小花', city: '武汉',  skills: ['js', 'css'] }

function fn(res) {}

fn(res) // 这里会报错,因为 res 可能为隐式的 any 类型

正确的解决方法为

const res = { name: '小花', city: '武汉',  skills: ['js', 'css'] }

type ResType = typeof res

function fn(res:ResType) {
}

fn(ResType)

keyof

keyof 用于某个数据或者某个类型的所有键,返回的是联合类型

interface obj = {
    name:string,
    age;number
}

keyof obj // ( name | age )

interface SonObj extends obj {
    hobby:'foods'
}

keyof SonObj // ( name | age | hobby )

keyof {a:1,b:2} // ( a | b )

in

in 关键字用来判断之是否属于某个值域中

type params = number | string 

if( '123' in params ) return true

泛型

泛型函数

在定义一个函数的时候,函数的返回值与函数的参数有关,虽然可以使用 any ,但是应用的整个运行失去了 TS 的保护而变得没有确定性,所以需要使用泛型来确定其类型

function  fn<T> ( value;T ) :T { return value }

其实就相当于是一个形参,通过泛型函数调用的时候传入到其中,规定参数与输出的类型,但是由于类型不确定性,所有有些语法不能直接书写

例如:

function getSum <T> (num1:number,num2:number) :T {
    return num1 + num2
}

这里会报错,因为 T 的类型是不确定的,所以无法使用 + 法

泛型函数的语法:

function 函数名 < 类型1,类型2,类型3...> (参数1:类型,参数2:类型,参数3:类型...) {
    return 与 类型相符的参数
}

利用类型推断简化调用

每次调用都需要传入泛型参数

fn<number> (10)

但是传入的参数只有一个类型,所以可以利用类型推断简化语法

fn(10)

泛型约束-收缩约束

因为泛型可以是任意类型,所有如果需要在泛型函数里面使用一些方法,必须对泛型进行收缩约束

收缩约束的两种方法:

  1. 指定具体类型
function fn<T>(arr:T[]) {
    return arr.length
}

从 T 类型的数据,具体为 T 类型的数组

  1. 添加约束
interface IString {
    length:numner
}
function fn<T> (str:T):T {
    return str
}

fn('123')

您可能需要使用泛型函数用于计算

泛型类型可以为任何类型,甚至可以为 Null 或者是 undefined ,如果直接用于加减是不满足运算条件的(左右两边要么是数字,要么是字符串)

解决办法一:

指定返回值为任意类型

function getSum<T>(num1:T,num2:T):any {
    return num1 + num2
}

解决办法二:

声明参与运算方指定类型的断言

function getSum<T>(num1:T,num2:T):T {
    return num1 as any + num2 as any
}

或者

function getSum<T>(num1:T,num2:T):any {
    return <any>num1 + <any>num2
}

解决办法三:

函数重载

function getSum(a:number,b:number):number
function getSum(a:string,b:number):string
function getSum(a:number,b:string):string
function getSum(a:string,b:string):string
function getSum(a:string,b:string,c?:number):string
function getSum(a:string,b:string,c?:string):string
function getSum(a:number,b:number,c?:number):string
function getSum(a:any,b:any,c?:any):string | number {
    // if(c) return a + b + c
    // else return a + b
    return a + b + ( c ? c : 0 )
    
}
console.log(getSum(1,2,3));

泛型接口

实例:

interface Iobj <T_string,T_number> {
    name:T_string
    age:T_number
}

const obj:Iobj<string,number> = {
    name:'ikun',
    age:18
}

语法:

定义:

interface 接口名 <类型1,类型2,类型3...> {
	属性名1:类型1,
	属性名2:类型2,
	属性名3: 类型3...
}

调用:

const 对象名:接口名<类型1,类型2,类型3...>

泛型工具类

typeof

typeof 用来获取一个变量的类型

typeof 变量

实例:

interface Iobj {
    name:string,
    age:18
}

const obj : Iobj = {
    name:'ikun',
    age:18
}

keyof

keyof 用来获取一个类型中的所有键值

keyof 类型
interface person {
    name:string,
    age:number,
}
    
console.log(keyof person)

Readonly

Readonly 用来定义一个类型,被添加注解该类型中的所有成员都是只读的,不可修改

type ReadonlyType = Readonly<类型>

实例:

interface IPerson {
    name:string,
    age:number
}

type ReadonlyPerson = Readonly<IPerson>

const obj:ReadonlyPerson = {
    name:'ikun',
    age:18
}

obj 被添加了 ReadonlyPerson 注解,那么被添加注解的成员将不可修改,以下操作就是错误的

obj.age = 29

但是可以使用类型交叉解决该问题

type ReadonlyPerson = Readonly<IPerson> & { foods:string }

Partial

用于定义一个类型,该类型中的所有成员都是可选的

type PartialType = Partial<类型>

实例:

interface Person {
    name:string,
    age:number
}

type PertialPerson = Partial<Person>

const obj:PartialPerson = {
    name:'ikun',
}

通过 Partail 定义的类型被注解给对象的时候,如果对象中的值没有注解中的属性,将不会报错

Pick

Pick 可以配合联合类型从一个类型中取出部分成员,形成一个新的类型

type PickType = Pick<类型 ,'成员1' | '成员2' | '成员3' ...>

实例

interface IPerson {
    name:string,
    age:number,
    hobby:string[]
}

type PickPerson = Pick<IPerson, 'name' | 'age' >

const obj:PickPerson = {
    name:'ikun',
    age:18
}