TS快速上手指南

463 阅读10分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」

引言

随着前端不断的发展前进,ts逐渐成为时代的宠儿,许多公司都逐步将ts引入项目开发中。作为一名合格的前端学习者,我们必须紧跟发展,扎实学好ts。

安装

1. 安装typescript

npm i -g typescript

2. 安装本地运行ts环境

npm i -g ts-node

3.创建文件夹并打开终端

tsc --init

4.建立ts文件1.ts,运行

ts-node 1.ts

一:基础类型

ts的基础类型约束包含许多,除了js中常见的基础类型外,还引入了any,unknown,never等类型

  • number
  • string
  • boolean
  • sybmol
  • any
  • unknown
  • never

1.类型约束后,无法赋给其他类型数据

    let str:string = "dzp"
    str = "xxx"
    str = 122 //报错

2.any可以赋值给任何数据类型,同时任何数据类型也可以赋值给any类型

    let node:any = 1
    let str:string ="xx"
    node = str
    

3.unkown只能赋值给any类型,同时任何数据类型可以赋值给unkown类型

let node:unknown = 1
node = "2"//ok
node = true //ok
let num:number = node  //error,unknown类型数据只能赋给unknown和any类型的数据

4.never

never表示永远不可能,通常用于死循环函数或者异常类型定义上

function fn():never {
    while(true) {
       console.log(1)
    }
}

二:数组

ts中数组的定义有两种方式

方式一:type[]

let arr:number[] = []
arr.push(1)//ok
arr.push("1")//error

方式二:Array<type>

let arr:Array<number> = []
arr.push(1)//ok
arr.push("1")/error

注意点

数组的两类定义方式都可以同时存在多种类型

   let arr1:(number|string)type = [1,"1"]
   arr.push(true)//false,只能存在number和string类型
   
   let arr2:Array<number|string> = [1,"1"]
   arr2.push(true)//false 同理

三:元祖

格式

[type1,type2,type3...]

ts中引入的元组类型是一类特殊的数组,它不仅要求类型相同,并且元素数量也必须相同。

let arr:[number,string,boolean] = [1,"da",true]
arr[3] = 1//error

四:对象

对象类型的约束就是由其他普通类型约束组成

  let objType:{
    name:string,
    age?:number//可添加age,也可以不添age属性
   } = {
    name:"dzp"
   }

console.log(objType)

五:函数

函数类型约束

指定了函数的参数类型和返回值类型

function fn1(name:string,age:number):string{
    
}

fn1("dzp",22)
fn1("dzp")//error,必须传递指定参数

函数可选参数

函数的可选参数只能出现参数的最后面,如下指定了age参数可以添加也可不添加

function fn(name:string,age?:number):string {
    if(age) return name
    return name+age
}

fn("dzp",22)
fn("xx")

函数默认参数

函数的默认参数添加后,可以选择传递,也可以选择不传递

 function fn(name:string,age:number=10){
     
 }
 fn("dzp")
 fn("dzp",22)

函数重载

函数重载:函数的名称相同,但是函数的参数类型或者函数的参数个数不同称为函数重载。

function fn(name:string,age:number):void;

function fn(name:number,age:string):void;

function fn(name:string|number,age:string|number) {

    if(typeof name === 'string') {

    console.log("执行第一个")

    } else {

    console.log("执行第二个")

    }

}
fn("dzp",22)//执行第一个

fn(22,"dzp")//执行第二个

六:枚举

ts中的枚举包含了三类

  1. 字符串枚举
  2. 数字枚举

字符串枚举

字符串枚举必须每个都添加默认值

enum str {
    a='a1',
    b='b1',
    c='c1'
}
let num:str = str.a
console.log(num)//‘a1’

数字枚举

数字枚举可以不添加默认值,不添加默认值默认从下标0开始赋值。数字枚举同时支持下标访问和对象访问两种方式。

enum e {
    a,
    b,
    c = 10,
    d
}
let num:e = e.b
console.log(num)//1

七:类型断言

类型断言

类型断言告诉编译器我自己确定了此变量的类型,编译器不要报错。小伙伴们可以按照类型强制转换去理解。

使用方式 变量 as type

下面这种场景会报错,因为ts无法确定你是否是字符串还是数字类型

function fn (name:string|number) {
  let len = name.length 
}

使用类型断言明确告诉编译器我是字符串

function fn (name:string|number) {
  let len = (name as string).length 
}

非空断言

非空断言指告诉编译器我的类型是非空的,排除空类型的情况

使用方式 变量!

function fn(name:string | null | undefined) {
  console.log(name.length)//error,编译器不确定是否是unll,undefined
  console.log(name!.length)//ok,明确告诉编译器是一定不是空
}

确定赋值断言

确定赋值断言指告诉编译器我的变量是赋值的

使用方式 变量!

 let name:string
 console.log(name);//error,没有赋值
 console.log(name!);//ok欺骗编译器,我赋值了,你不要检测。
 

八:类型守卫

类型守卫就是使用 typeof、in、instanceof等类型判断约束类型的范围。

九:类型别名 type

类型别名:给已有的类型换个名称

type str = string
let s:str = "age"

注意点

1. type不可以重复定义

 type str = string
 type str = number;//error

2. 可以定义普通数据类型

 type str = string
 let s:str = "abc"

3. 可以定义对象类型

type objs = {name:string,age:number}

let obj:objs = {name:"aaa",age:2}

十:接口 interface

接口和类型别名十分相似,都是对类型做进一步的约束,特殊的是接口只能作用在对象、函数上。

1. 特殊约束

?:表示该属性对象可以添加,也可以不添加

readonly key:表示该属性只能读,无法修改和删除

[fn:string]:any 表示对象可以添加任意额外属性

interface inter {
  name:string,
  age:number,
  sex?:number,
  readonly height:number,
  info(name:string):number,//定义对象内部的方法
  [fn:string]:any//可选属性
}

let obj:inter = {
  name:"dzp",
  age:22,
  height:100,
  info(name){
      return 1
  },
  other1:'oo',
  other2:100
}

2.继承

interface是可以通过extends完成属性的继承。

interface father{
  name:string
}

interface son extends father {
  age:number
}

let o:son = {
  name:"dzp",age:22
}

3.重复定义

与type不同的是,type定义相同的类型变量会报错,而interface定义相同的类型变量是允许的,并且还会自动合并多次定义的属性

interface inter{
  name:string
}
interface inter{
  age:number
}

let o:inter = {
  name:"dzp",
  age:22
}

4.interface和type的索引获取类型

interface和type都可以通过索引构造新的类型

  1. type

     type t1={
       name:string
       age:number
     }
     type tt1 = t1["name"|"age"]//string|number
    
  2. interface

     interface t1{
          name:string
          age:number
     }
     type tt1 = t1["name"|"age"]//string|number
    

5.type与interface区别

  1. type不可以重复定义,interface可以重复定义
  2. type可以约束普通数据类型也可以作对象类型,而interface只能用于对象数据类型约束
  3. type的拓展通过&,interface的拓展通过继承实现。

十一:class

  1. class引入了成员属性,成员方法,静态属性,静态方法等

  2. 属性前加#表示私有属性

  3. 属性前加readonly表示只读属性,不可以修改。

  4. 引入了public,private,protected修饰符

     class People {
       name!:string//成员属性
       #age!:number//私有成员属性
       static sex:number = 1//静态属性
       constructor(name:string,age:number) {
         this.name = name
         this.#age = age
       }
       static getName():string {//静态方法
         return this.name
       }
       getAge():number{//成员方法
         return this.#age
       }
     }
    

十二:范型

范型是ts中的重点与难点,学会范型其它知识点也就十分容易了。此处的范性和java,c++的范性约束是一样的,学过的同学肯定感到十分熟悉。

1. 范性在函数的应用

//普通函数的定义
function fn<T,V>(name:T,age:V):T {
    console.log(name)
    return name;
}

//箭头函数中定义
const fn = <T,V>(name:T,age:V):T => {
    return name;
}

fn<string,number>("dzp",22)

2. 范性在接口的应用

interface inter<T> {
    name:T
}
let o:inter<string> = {name:"dzp"}

3. 范性在类型别名的应用

type t<T> = {
    name:T
}
let o:t<string> = {name:"dzp"}

4. 范性在类的应用

class clacArray<T,U>{
    name!:T
    age!:U
}

十三: 常见关键词搭配

extends

检验是否存在某个属性K

type type1 = {
  length:number
}
function fn<T extends type1> (name:T) {
  return name.length
}
fn<number[]>([1,2,3])//OK
fn<string>("dzp")//ok
fn<number>(1)//error

typeof

根据变量生成其类型约束

let obj = {name:"dzp",age:22}
type t =typeof obj
//此时的t相当于 t={name:string,age:number}

keyof

生成接口类型的所有k(此次是key,而不是生成所有类型)

type t = {name:string,age:number}

type t2 = keyof t

//此次相当于 type t2 = 'name' | 'age'

[]索引访问

[]可以获取type或者接口interface类型定义中某个属性的具体类型

 type o = {name:string,age:number}
 
 type t = o['name']
 
 //此处相当于type t = string
 
 

in

映射遍历枚举类型

type t8 = name|age

type t9 = {
  [p in t8]:any
}

//此处相当于 type t9 = {name:any,age:any}

Partial

将接口或type定义的对象类型全部变成可选的?

 type t = {name:string}
 
 
 let o:t<Partial> = {}//name属性变成可选类型
 

Required

将接口或type定义的对象类型全部变成必选的类型

 type t = {name?:string}
 
 
 let o:t<Required> = {name:'dzp'}//name属性变成必选类型
 

Pick

从接口或type类型定义中挑选几个属性形成新类型

   type t = {name:string,age:number}

   let o:t<Pick,'name'> = {name:"dzp"}
   

Omit

与pick相反,从接口或type定义类型中排除几个属性形成新类型定义

 type t = {name:string,age:number}

 let o:t<Omit,'name'> = {age:22}

Extract

常用于从联合类型中选择几个类型

type t = string | number
let o:Extract<t,string> = 'name'

Exclude

常用于从联合类型中删除几个类型

type t = string | number
let o:Exclude<t,string> = 22

Record

Record<K,V> 通常k是联合类型作为key存在,而v通常作为接口或者type定义的对象类型约束。

 type t = 'name' | 'age'
 
 type t1 = {
  title:string
 }
 
 let o:Record<t,t1> = {
  name:{title:"dzp1"},
  age:{title:"dzp2"},
 }

Readonly

Readonly 修饰的类型是只读的

   interface tss {
      name:string,
      age:number
    }
    type t1 = Readonly<tss> 

    let o:t1 = {name:'dzp',age:22}
    o.age = 23//error

    

十四:+-运算符

+-可以对ts的修饰符进行添加和删除。

+?:没有?的添加修饰符?

-?:有?修饰符的删除?

案例:类型全部变成必选类型

  type MyRequired<T> = {
      [k in keyof T]-?:T[k]
  }
  interface P {
    name:string
    age?:number
  }
  type r = MyRequired<P>;
  let o:r = {name:"dzp",age:22}//age必须填

十五:infer

infer可以对数据类型进行自动推断,infer必须出现在条件类型中才可以使用。infer后面跟的就是自动推导的类型。

使用

在类型的前面加上infer即可

//推断对象类型的属性类型
type get2 = {name:string} 
type get3 = get2 extends {name:infer test} ? test:never

    

案例一:对数组类型的内部类型进行推断

type ArrType<T> = T extends (infer e)[] ? e :never
type Result = ArrType<number[]>

案例二:对函数类型参数进行类型推断

type fn = (name:string) => any

type getFunction<T> = T extends (...args:infer tpye)=>any ? tpye:never
type get1 = getFunction<fn>

十六:declare

declare用来对模块类型进行类型声明,其特点有

  1. 文件必须是 *.d.ts

  2. 文件包含的是类型声明,而不是具体实现

  3. declare module中模块必须和导入的模块相对应

     //index.ts使用lodash模块
     import _ from "lodash"
    
     //1.d.ts添加lodash类型声明
     declare module "lodash" {
       export xxx
     }
    

1. ts项目导入js文件

ts项目直接调用js文件是报错的,会出现警告没有类型声明,因此需要创建.d.ts文件定义声明。 如下所示,通过declare module来为导入的getName方法添加类型声明。

test.js

    export function getName(){
        return "dzp";
    }

index.ts

    import {getName} from "./test.js"

    getName();

a.d.ts

    declare module "*" {
      type getName = ()=>string
      export {getName}
    }

2.为模块添加类型拓展

很多时候项目导入的第三方包提供的声明文件(.d.ts)无法满足我们的类型约束需求,因此我们需要在其基础上添加类型拓展。

vue-router例子

如下是为vue-router提供ts类型约束,约束每个meta参数必须是如下类型

import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import HomeView from '../views/HomeView.vue'
declare module 'vue-router' {
  interface RouteMeta {
    // 是可选的
    isAdmin?: boolean
    // 每个路由都必须声明
    requiresAuth: boolean
  }
}
const routes: Array<RouteRecordRaw> = [
  {
    path: '/',
    name: 'home',
    component: HomeView,
    meta:{
        requiresAuth:true
    }
  },
  {
    path: '/about',
    name: 'about',
    component: () => import(/* webpackChunkName: "about" */ '../views/AboutView.vue'),
    meta:{
        requiresAuth:true
    }
  }
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

在lodash库中添加类型拓展

1.d.ts

    declare module "lodash" {
      export type dzp=number
    }

index.ts:测试使用lodash

    import _,{dzp} from "lodash"

    let a:dzp = "123"//error
    let b:dzp = 123//ok