【TypeScript】入门

220 阅读8分钟

TypeScript的优点/项目使用TS的好处❗

优点描述
静态类型检查通过静态类型检查,可以在编码阶段就发现和修复错误,提高代码的质量。
提高项目的可维护性通过类型注解和接口,其他开发者可以更容易理解和使用你的代码,可以提高代码的可读性和可维护性。
降低团队协作成本在团队开发中,TypeScript的类型系统可以帮助所有人都对代码有相同的理解,降低沟通成本
更好的兼容性TypeScript支持最新的ECMAScript特性,并可以将它们编译到旧版本的JavaScript,以便在老版本的浏览器中运行。

(一)基本变量的类型限制

0. 基本类型

  • TypeScript最基本的数据类型包括:布尔、数字、字符串、null、undefined

1. any:任意类型

当不确定某个变量是什么类型时,可以使⽤any作为这个变量的类型

2. enum:枚举类型

可以把类型限制在指定的场景之内

enum Color {Red, Green, Blue} 
let c: Color = Color.Green;

3. never:任何类型的子类型

任何类型的子类型,也就是说 never 可以赋值给任何类型。

const a: number = '' as never;
const b: object = '' as never;

4. void:没有任何类型

void类型像是与any类型相反,它表示没有任何类型,当一个函数没有返回值时,你通常会见到其返回值类型是 void

5. &:交集提取出两个类型都拥有的类型

6. |:类型联合

定义枚举类型,这样可以把类型限制在指定的场景之内

// 定义course1变量的类型为字符串或者数字,赋值为这两个类型都不会报错
let course1 : string|number = '玩转vue 3'
course1 = 1
// course1 = true // 报错 

// type⽤来限制变量只能赋值为⼏个字符串的⼀个,score的取值只能是代码中三个值之⼀。
type courseScore = '好' | '⾮常好' | '嘎嘎好' 
let score1 :courseScore = '好' 
let score2 :courseScore = '⼀般好' // 报错

(二)对象的类型限制(interface

通过interface接⼝可以定义对象的类型限制

标题说明
某个类型+[] 表示语法定义类型为某个类型组成的数组,等同于Array<某个类型>
?表示 设置 为可选属性
readonly表示 设置为只读属性,如果对其进⾏修改就会报错
// 通过interface接⼝可以定义对象的类型限制
interface 对象的类型限制 {
    名称: string,
    幸运数字: number[],//使⽤number[] 语法定义类型为数字组成的数组
    爱好?: string | boolean,// 通过 ?设置 为可选属性
    readonly 国籍: string // readonly设置为只读属性,如果对其进⾏修改就会报错。
}
let person: 对象的类型限制 = {
    名称: '哈哈',
    幸运数字: [0, 1],
    爱好: false,
    国籍: '中国'
}
// person.国籍='美国'//报错,只读不可修改

(三)函数的类型限制

  • 函数的定义,参数和返回值本质上也是变量的概念,都可以进⾏类型的定义
  • ⼤致语法:function 函数名(参数:参数类型):返回值类型{}

1. function的方式定义函数

// 1. function的方式定义函数
function add(x: number, y: number): number {
    return x + y;
}
add(1, 2);

2. 使用变量的方式定义函数(可读性差,不推荐)

let add1: (x: number, y: number) => number = function (x: number, y: number): number {
    return x + y
}
add1(2, 3)

3. 使⽤type关键字去定义函数的类型(推荐)

type addType = (a: number, b: number) => number
let add2: addType = function (x: number, y: number): number {
    return x + y
}

4. 使⽤interface关键字去定义函数的类型(推荐)

interface addInterface {
    (a: number, b: number): number
}
let add3: addInterface = function (x: number, y: number): number {
    return x + y
}

5. 函数重载的方式

  • 如果你的函数本来就⽀持多个类型的参数,下⾯的代码中reverse函数既⽀持数字也⽀持字符串。
  • 我们的要求是:如果参数是数字,返回值也要是数字;参数是字符串,返回值也只能是字符串
    所以参数和返回值都⽤ number|string就没法精确地限制这个需求。
  • 我们需要使⽤函数重载的⽅式,定义多个函数的输⼊值和返回 值类型,更精确地限制函数的类型。
// 函数重载
function reverse(x: number): number
function reverse(x: string): string
function reverse(x: number | string): number | string | void {
    // typeof判断参数的类型
    // 如果参数是数字,返回值也要是数字
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    }
    // 参数是字符串,返回值也只能是字符串
    else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}

(四)宿主环境⾥的类型

属性说明
Windowwindow的类型
HTMLElementdom元素类型
NodeList节点列表类型
MouseEvent⿏标点击事件的类型
IArguments⿏函数默认入参的类型
// window的类型
let w: Window = window
// dom元素类型
let ele: HTMLElement = document.createElement('div')
// 节点列表类型
let allDiv: NodeList = document.querySelectorAll('div')
// 标点击事件的类型
ele.addEventListener('click', function (e: MouseEvent) {
    const args: IArguments = arguments
    w.alert(1)
    console.log(args)
}, false)

(五)泛型

1. 类型变量 <T>

  • <T>让我们拥有了给函数的参数定义类型变量的能⼒;
  • 当我们需要返回值的类型和参数⼀致时,可以在函数名之后使⽤<>定⼀个泛型T
  • 你可以理解这个T的意思就是给函数参数定义了⼀个类型变量,会在后⾯使⽤;
    相当于【type T = arg的类 t 型】,返回值使⽤T这个类型就完成了这个需求
// 类型变量的参数是T,通过传入不同的参数,控制不同的类型变化
function identity<T>(arg: T): T { return arg }
// 这个T就是string,所以返回值必须得是string 
identity<string>('玩转vue 3全家桶')
// 这个T就是number,所以返回值必须得是number
identity<number>(1)

2. keyof: 获取已知类型的属性(key)列表

keyof语法可以获得已知类型的属性(key)列表,可以理解成获取对象的所有key值

interface VueCourse5 {
    name: string, 
    price: number
}
// 使⽤keyof语法获得已知类型VueCourse5的属性列表,相当于 ‘name’|‘price’:
type CourseProps = keyof VueCourse5 // 等效于 "name" | "price"
let k: CourseProps = 'name' // k 只能是name和price选⼀个 
// let k1: CourseProps = 'p' // 报错,改成price就是对的

3. extends:类型系统中的条件判断

  • 用法:类型三元表达式(T extends U) ? X : Y
  • 使⽤extends来实现类型系统中的条件判断
  • extends相当于TypeScript世界中的条件语句
// 定义类型函数ExtendsType,接受泛型参数T后,
// 通过判断T是不是布尔值来返回不同的类型字符串
type ExtendsType<T> = (T extends boolean) ? "重学前端" : "玩转Vue 3" 
type ExtendsType1 = ExtendsType<boolean> // type ExtendsType1='重学前端' 
type ExtendsType2 = ExtendsType<string>  // type ExtendsType2='玩转Vue 3'

4. infer :给extends之后的变量设置类型变量

  • 用法:type InferArray<T> = T extends (infer U)[] ? U : never;
  • infer可以在extends之后的变量设置类型变量,更加细致地控制类型。
  • infer语法的限制如下:
    • infer只能在条件类型的 extends 子句中使用
    • infer得到的类型只能在true语句中使用, 即X中使用
// 如果T是⼀个函数,并且函数返回类型是P就返回P
// infer P的意思就是泛型T是函数类型() => infer P,并且这个函数类型的返回类型是P
type ReturnType1<T> = T extends ( () => infer P ) ? P : never

type Foo = () => CourseObj 
type Foo1 = ReturnType1<Foo>

5. in 关键字:遍历

  • in关键字可以理解为TypeScript世界中的遍历
type Courses = '玩转Vue 3' | '重学前端'
type CourseObj = {
    [k in Courses]: number // 遍历Courses类型作为key 
}
// 上⾯的代码等于下⾯的定义
// type CourseObj = {
//     '玩转Vue 3': number;
//     '重学前端': number;
// }

6. 泛型的综合运用

// K extends keyof T:限制K的类型必须是T的属性之⼀
// T[K]是值的类型 
function getProperty<T, K extends keyof T>(obj: T, name: K): T[K] {
    return obj[name]
}
const coursePrice: CourseObj = {
    "玩转Vue 3": 129,
    "重学前端": 129
}
getProperty(coursePrice, '玩转Vue 3')
getProperty(coursePrice, '不学前端') // 报错

(六)Pick 和 Omit

1. Pick

  • 从类型定义的属性中,选取指定一组属性,返回一个新的类型定义。
  • 使用
type Pick<T, K extends keyof T> = { 
    [P in K]: T[P]; 
};
  • 示例:
interface Person {
  name: string;
  age: number;
  id: number;
  sex: 0 | 1;
}

// 如果不需要 age 这个属性,只选取name和id
type Woman = Pick<Person, "name" | "id">;

// 此时 Woman 等效于 Female
interface Female {
  name: string;
  id: number;
}

2. Omit

  • Omit 与 Pick 作用相似,只不过 Omit 是:以一个类型为基础支持剔除某些属性,然后返回一个新类型。
  • 使用
type Omit<T, K extends string | number | symbol> = {
  [P in Exclude<keyof T, K>]: T[P];
};

  • 示例
interface User {
  id: number;
  name: string;
  age: number;
  sex: 0 | 1;
  tel: number;
}

type EditUser = Omit<User, "id">; // 就是在 User 的基础上,去掉 id 属性

(七)实战 & 练习

1. TypeScript在Vuex4中使用TS实战分享

2. 例题

需求:我们该如何定义Interface API,使其能够限制:

  • request只能有buy和comment两个请求地址,
  • 并且comment请求的参数中message是必传项
// 实战练习
import axios from 'axios' function request(url: string, obj: any) { return axios.post(url, obj) }
interface Api { }
request('/course/buy', { id: 1 })
request('/course/comment', { id: 1, message: '嘎嘎好看' })
request('/course/comment', { id: 1 }) //如果message必传 怎么类型提醒缺少参数 
request('/course/404', { id: 1 }) //接⼝不存在 类型需要报错

实现

import axios from 'axios'
interface Api {
    '/course/buy': { id: number },
    '/course/comment': { id: number, message: string }
}
function request<T extends keyof Api>(url: T, obj: Api[T]) {
    return axios.post(url, obj)
} 

(八)知识回顾

  • 讲一下 泛型、 infer 关键字、void 和 never 区别。