Vue3 开发者的 TypeScript 入门:从泛型 T 到 Promise,彻底搞懂类型推导
作为一名前端开发,在 Vue3 项目里 TypeScript 基本已经成为绕不开的东西。
刚开始接触 TS 的时候,我最大的感受就是:语法看起来不难,但是一旦出现泛型、keyof、typeof、extends,就很容易懵。
尤其是下面这种代码:
r
function getProperty<T, K extends keyof T>(
obj: T,
key: K
) {
return obj[key]
}
第一次看到的时候,真的会觉得"这是什么鬼"。
后来把它拆开理解之后,发现其实并没有那么复杂。
一、先理解 TypeScript 到底解决了什么问题
JavaScript 是动态类型语言:
arduino
const user = {
name: '张三',
age: 20
}
你完全可以写:
user.name
user.age
user.xxx
但是 user.xxx 到运行的时候才可能发现有问题。
TypeScript 的作用之一,就是在开发阶段帮助我们发现这些问题。
比如:
typescript
interface User {
name: string
age: number
}
现在:
css
const user: User = {
name: '张三',
age: 20
}
如果写:
user.xxx
编辑器就可以直接提示错误。
这就是 TS 最基础也最实用的价值。
二、泛型 T 到底是什么?
泛型刚开始看起来比较抽象。
比如:
r
function getFirst<T>(arr: T[]): T {
return arr[0]
}
这里的 T 可以理解成一个类型占位符。
如果传进去:
scss
getFirst([1, 2, 3])
TypeScript 会推断:
ini
T = number
所以:
css
T[]
就相当于:
css
number[]
而返回值:
r
T
就是:
typescript
number
如果传进去:
css
getFirst(['张三', '李四'])
那么:
ini
T = string
所以返回值就是 string。
因此泛型最核心的思想其实就是:
先不确定类型,等真正使用的时候再确定。
三、Promise 其实也是同样的思想
前端开发中经常看到:
javascript
Promise<User>
这个其实并不难。
假设:
typescript
interface User {
id: number
name: string
}
然后:
javascript
async function getUser(): Promise<User> {
// 请求接口
}
这里的 Promise<User> 可以理解成:
这个异步函数最终会得到一个 User。
所以:
csharp
const user = await getUser()
此时:
sql
user
就是 User 类型。
那么:
user.name
就是 string。
python
user.id
就是 number。
四、实际项目中经常出现的 API 类型
比如后端接口统一返回:
csharp
interface ResponseData<T> {
code: number
message: string
data: T
}
用户列表:
typescript
interface User {
id: number
name: string
}
那么接口可以写成:
javascript
async function getUserList(): Promise<ResponseData<User[]>> {
// 请求后端
}
看到这里不要害怕,我们从外往里拆:
swift
Promise<ResponseData<User[]>>
↓
await
↓
ResponseData<User[]>
↓
data
↓
User[]
所以:
csharp
const res = await getUserList()
那么:
kotlin
res.data
就是:
css
User[]
继续:
css
res.data[0]
就是:
sql
User
最后:
css
res.data[0].name
就是:
c
string
这就是泛型在真实项目里的价值。
五、keyof 又是什么?
keyof 可以简单理解为:
获取一个类型的所有属性名。
比如:
typescript
interface User {
id: number
name: string
age: number
}
那么:
sql
keyof User
得到的就是:
arduino
'id' | 'name' | 'age'
注意,这里获取的是属性名,不是属性值的类型。
所以不是:
typescript
number | string
而是:
arduino
'id' | 'name' | 'age'
六、typeof + keyof 又是什么?
如果不是 interface,而是一个普通对象:
yaml
const product = {
id: 1,
title: '手机',
price: 2999
}
可以通过:
csharp
typeof product
获取这个变量的类型。
然后:
csharp
keyof typeof product
就可以得到:
arduino
'id' | 'title' | 'price'
可以简单记成:
csharp
typeof
↓
拿变量的类型
keyof
↓
拿类型的属性名
七、K extends keyof T 为什么这么写?
再回到最开始那个看起来很复杂的函数:
r
function getProperty<T, K extends keyof T>(
obj: T,
key: K
) {
return obj[key]
}
它其实是在做一件很简单的事情:
传入一个对象,再传入这个对象合法的属性名,然后返回这个属性对应的值。
例如:
yaml
const product = {
id: 1,
title: '手机',
price: 2999
}
那么:
scss
getProperty(product, 'title')
最终得到:
arduino
'手机'
而:
scss
getProperty(product, 'price')
得到:
yaml
2999
但是:
scss
getProperty(product, 'name')
就会报错,因为 product 根本没有 name 这个属性。
这里:
java
K extends keyof T
可以简单理解为:
K 必须是 T 的属性名之一。
八、总结
今天重新梳理了一遍 TS 之后,我发现 TypeScript 并没有想象中那么玄学。
很多复杂写法,其实都是由几个简单概念组合起来的:
markdown
interface
↓
定义对象结构
泛型 T
↓
类型占位符
T[]
↓
T 类型的数组
Promise<T>
↓
最终得到 T
keyof
↓
获取类型的属性名
typeof
↓
获取变量的类型
K extends keyof T
↓
限制 K 必须是 T 的属性名
对于 Vue3 开发来说,我觉得 TypeScript 不一定非要一开始就研究特别复杂的类型体操。
先把泛型、接口、联合类型、类型缩小、keyof、typeof 这些真正用到项目里的知识搞明白,比一上来研究高级类型更重要。
后面准备继续学习 Vue3 + TypeScript,把这些东西真正用到 ref、defineProps、defineEmits、API 请求以及项目类型设计里面。
学习 TS 的过程,感觉就是从"看不懂"到"拆开就懂"。
如果你也在学 Vue3 + TypeScript,希望这篇文章能对你有所帮助。
#前端开发 #Vue3 #TypeScript #程序员 #学习笔记 #Web前端