🚀 Vue3 + TypeScript 学习笔记:从泛型 T 到 Promise<T>,终于搞懂了 keyof 和 typeof

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,把这些东西真正用到 refdefinePropsdefineEmits、API 请求以及项目类型设计里面。

学习 TS 的过程,感觉就是从"看不懂"到"拆开就懂"。

如果你也在学 Vue3 + TypeScript,希望这篇文章能对你有所帮助。

#前端开发 #Vue3 #TypeScript #程序员 #学习笔记 #Web前端

相关推荐
跟着群主去吃肉1 小时前
Cinemachine的应用-第三人称视角
前端·unity3d·游戏开发
东方小月1 小时前
从零开发一个 Coding Agent(十):实现 CLI 参数解析与静态命令
前端·人工智能·开源
阿黎梨梨1 小时前
Next.js 全栈开发:从 SPA 的痛点到 SSR 的破局之道
前端·后端
沐道PHP1 小时前
CRMEB多店版diy装修位置偏移修复
前端
犀利的毛豆豆1 小时前
useCallback
前端
犀利的毛豆豆1 小时前
React.memo和useCallback组合使用
前端
夏幻灵2 小时前
Vue 2 与 Vue 3 在应用初始化上的设计差异与技术演进
前端·javascript·vue.js
捌年2 小时前
5分钟搞定工作周报!TRAE Work自动梳理零散工作记录实战
前端
悟空瞎说2 小时前
Three.js 架构实践:用 MVC 模式组织你的 3D 应用
前端·javascript