跟着我十分钟学会 TypeScript

5,215 阅读7分钟

接下来在这一节,我们将利用一整节的时间去聊一下 Typescript,希望你在学完这一节之后,能对 TypeScript 有一个全面的认识

安装 Typescript

npm install -g typescript

提出问题

TypeScript 是用来做什么的 ❔

我们在学习一门新课程时,都会有这种疑惑 😖 那就是这个东西我们学来是干什么用的

现在,我就用一个最简单的例子来回答你

if ("" == 0) {

// 他们相等!但是为什么呢??

}

像上面是一行最简单的 javaScript 判断代码,但是令我们疑惑的是,这两个值为什么会相等

这是因为 0 与 " " 转换成布尔型都是 false 的,也正是由于 JavaScript 的等于运算符会试图强制值相等,才出现这种匪夷所思的事情


下面,我们再换成一个简单的 TypeScript 小例子

在下面的代码中,我们定义了一个字符串类型 name,当我们把 String 类型的变量赋值给 Number 时,就会报错

let name:string = '《Vue3从入门到就业》'

name = 888 //类型报错

由此不难看出,TypeScript 可以在 JavaScript 的基础上,对变量的数据类型加以限制

那么下面就让我们继续探讨 TypeScript , 具体看看它带来了什么特性和语法


TypeScript 入门

1. any 类型

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

你可以用 any 标记任何属性,可以修改任何数据,访问任何方法也不会报错

也就是说,在 TypeScript 中,当你把变量的类型标记为 any 后,这个变量的使用就和 JavaScript 没啥区别了,错误只会在浏览器里运行的时候才会提示

let name : any = 4
name = '《Vue3从入门到就业》'
name = true


// 允许调用任何方法:
notSure.getName()

2. enum 枚举

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

下面我们来尝试做数字枚举,然后这里面的值,枚举成员会被赋值为从 0 开始递增的数字

enum Direction { Up, Down, Left,Right }
console.log(Direction.Up)  // 0

还有一个神奇的点是这个枚举还做了反向映射
console.log(Direction[0])  // Up

那我们再来试试字符串的枚举

// 字符串枚举
enum Direction {
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT',
}
const value = 'UP'

console.log( value === Direction.Up )   // true

3. | 实现类型联合

然后我们可以使用 | 实现类型联合,通过组合的方式组合出新的类型

下面的代码中我们定义 name 变量的类型为字符串或者数字,赋值为这两个类型都不会报错

let name : string|number = '《Vue3从入门到就业》'

name = 8888
name = true // 报错

还可以用来限制变量只能赋值为几个字符串的一个,score 的取值只能是代码中三个值之一

type Fruits = '香蕉' | '苹果' | '西瓜'

let Fruits1 :Fruits = '香蕉'

let Fruits2 :Fruits = '汉堡包'   // 报错

4. interface 接口

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

比如下面我们定义一个数据 Person (人)

interface Person {
  name: string;
  age: number;
}

接着定义一个变量 Chinese (中国人),它的类型是 Person

这样,我们就约束了 Chinese 的形状必须和接口 Person 一致

let Chinese: Person ={
  name: '黄勇超',
  age: 22
}

当有时候我们希望不要完全匹配一个形状,那么可以用可选属性:

interface Person {
    name: string;
    age?: number;
}
let Chinese: Person = {
    name: ''
}

有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性

interface Person {
    name: string;
    age: number;
    readonly id:string
}

let Chinese: Person = {
   name: '',
   age: 22
   id:"88888"
}

Chinese.id = '99999' // 报错

5. 函数的类型限制

函数的定义,参数和返回值本质上也是变量的概念,都可以进行类型的定义

基础语法

function 函数名(参数:参数类型):返回值类型{}

比如下面的代码中我们定义了参数 x 和 y 是数字,返回值也是数字

function add(x: number, y: number): number {
  return x + y
}

同样的,我们也可以设置可选参数

function add(x: number, y: number, z?: number): number {
  if (typeof z === 'number') {
    return x + y + z
  } else {
    return x + y
  }
}

6. type 类型别名

类型别名,就是给类型起一个别名,让它可以更方便的被重用

let sum: (x: number, y: number) => number

const result = sum(1,2)

type PlusType = (x: number, y: number) => number

let sum2: PlusType

支持联合类型

type StrOrNumber = string | number
let result2: StrOrNumber = '123'
result2 = 123

字符串字面量

type Directions = 'Up' | 'Down' | 'Left' | 'Right'
let toWhere: Directions = 'Up'

TypeScript 轻进阶

讲完上面的内容,你就已经能使用 TypeScript 实现很多项目的开发,把所有变量和函数出现的地方都定义好类型,就可以在编译阶段提前规避出很多报错,接下来我们再往下进行轻进阶的课程

1. 类型断言

as 关键字,告诉 TypeScript 编译器,你没法判断我的代码,但是我本人很清楚,这里我就把它看作是一个 string,你可以给他用 string 的方法

function getLength( input: string | number ): number {
  const str = input as string
  if (str.length) {
    return str.length
  } else {
    const number = input as number
    return number.toString().length
  }
}

2. Generics 泛型

泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性

如下面的代码

function getTitle(title) {
  return title
}
const result = getTitle('《Vue3从入门到就业》')

这时候我们发现了一个问题,我们传入了字符串,但是返回了 any;这时候我们使用泛型

我们在函数名之后使用 <> 定一个泛型 T,你可以理解这个 T 的意思就是给函数参数定义了一个类型变量,会在后面使用,相当于【type T = title 的类型】,返回值使用 T 这个类型就完成了这个需求

function getTitle<T>(title: T): T {
  return title
}
const result = getTitle('《Vue3从入门到就业》')

console.log(result)  // '《Vue3从入门到就业》'

泛型也可以传入多个值

function getTitle<T, U>(title: [T, U]): [U, T] {
  return [title[1], title[0]]
}

const result = getTitle(['《Vue3从入门到就业》', '黄勇超'])

有了泛型之后,我们就有了把函数参数定义成类型的功能,我们就可以实现类似高阶函数的类型函数

下面的代码中我们使用 keyof 语法获得已知类型 Person 的属性列表,相当于 "name"| "price" :

interface Person {
    name:string,
    age:number
}

type Chinese = keyof Person // 只能是 name 和 age 选一个

let k1:Chinese = 'name'

let k2:Chinese = 'age' // 改成 age

keyof 可以帮助我们拆解已有类型;下一步我们需要使用 extends 来实现类型系统中的条件判断

我们定义类型函数 name,接受泛型参数 T 后,通过判断 T 是不是布尔值来返回不同的类型字符串,我们就可以通过 name 传入不同的参数去返回不同的类型

🔔: T extends U ? X : Y    类型三元表达式


type name <T> = T extends boolean ? " Vue 2 " : " Vue 3 "

type name1 = name<boolean> // type name1= ' Vue 2 '

type name2 = name<string> // type name2= ' Vue 3 '

extends 相当于 TypeScript 的条件语句,然后 in 关键字可以理解为 TypeScript 的遍历

下面的代码中我们通过 k in name 语法,相当于遍历了 name 所有的类型作为 nameObj 的属性

type name = ' Vue 3 '|' Vue 2 '

type nameObj = {
    [k in name]:number // 遍历name类型作为key
}


上面的代码等于下面的定义

type nameObj = {
    Vue 3 : number;
    Vue 2: number;
}

接下来我们再来讲解最后一个关键字 infer

让我们拥有了给函数的参数定义类型变量的能力,infer 则是可以在 extends 之后的变量设置类型变量,更加细致地控制类型

下面的代码中我们定义了 ReturnType 类型函数,目的是返回传入函数的返回值类型

infer P 的意思就是泛型 T 是函数类型,并且这个函数类型的返回类型是 P

type Foo = () => CourseObj

如果 T 是一个函数,并且函数返回类型是 P 就返回 P

type ReturnType1<T> = T extends ()=>infer P ?P:never

type Foo1 = ReturnType1<Foo>