资讯动态

两小时带你入门 TypeScript(中):泛型与 React / Vue 项目实战

发布时间:2026/10/14 3:28:51 来源:尧图企业网站定制
在上篇中我们已经了解了 TypeScript 的基本类型、类型推断、函数类型、联合类型以及interface和type的区别。掌握这些语法之后我们已经能够读懂不少 TypeScript 代码但要真正应用到前端项目中还需要理解泛型以及如何为接口数据、组件 Props 和状态添加类型。本文就从泛型入手结合 React 和 Vue 的实际开发场景看看 TypeScript 是如何融入前端工程的。一、泛型让类型也可以成为参数泛型Generics是 TypeScript 中非常重要的特性。假设我们需要编写一个函数传入什么数据就返回什么数据function identity(value: number): number { return value }这个函数只能接收数字。如果想同时支持字符串可以使用联合类型function identity( value: string | number ): string | number { return value }虽然能够处理两种类型但存在一个问题const result identity(hello)此时result的类型仍然是string | numberTypeScript 无法通过这个函数声明确定返回值一定是字符串。原因是联合类型只描述了数据可能具有哪些类型并没有表达输入与输出类型之间的对应关系。这时候就需要泛型。1.1 泛型的基本使用function identityT(value: T): T { return value }其中的T就是泛型参数可以理解成一个暂时没有确定具体类型的类型变量。整段代码可以拆成三部分T声明一个类型参数。value: T参数的类型由T决定。): T返回值的类型也由T决定。调用函数时可以明确指定类型const a identitynumber(123) const b identitystring(hello)第一次调用时T是number第二次调用时T是string。因此a的类型是numberb的类型是string。实际开发中TypeScript 通常可以根据传入的参数自动推断泛型类型const a identity(123) const b identity(hello)所以泛型的核心价值不是简单地支持多种类型而是在代码复用的同时保留类型之间的对应关系。它与any不同。any会绕过很多类型检查而泛型可以在具体类型确定后继续提供类型约束。1.2 泛型约束 extends并不是所有泛型参数都能随意使用属性。例如function getIdT(obj: T) { return obj.id }这段代码会报错因为T可能是字符串、数字等不具有id属性的类型。可以通过extends添加约束function getIdT extends { id: number }( obj: T ) { return obj.id }这里表示T必须满足{ id: number }这一结构。getId({ id: 1, name: 张三 }) // 正确 getId({ name: 张三 }) // 报错需要注意这里的extends并不是 JavaScript 中的类继承而是在类型层面限制泛型参数必须满足一定条件。掌握T和T extends ...就能看懂很多前端库的泛型声明。二、接口数据类型泛型在前端的典型应用在实际项目中前端经常需要向后端请求数据。例如后端提供一个用户列表接口{ code: 200, message: success, data: [ { id: 1, name: 张三 } ] }首先可以使用interface描述用户对象interface User { id: number name: string }但不同接口的响应通常有相似的结构比如都包含code、message和data只不过data的类型不同。这时可以定义一个泛型接口interface ApiResponseT { code: number message: string data: T }这里的T表示data的类型由使用者决定。例如type UserResponse ApiResponseUser type UserListResponse ApiResponseUser[]前者的data是单个用户对象后者的data是用户数组。这样就不需要为每个接口重复定义整个响应结构。2.1 Axios 与泛型结合假设使用 Axios 获取用户列表import axios from axios async function getUserList(): PromiseUser[] { const res await axios.getApiResponseUser[]( /api/users ) return res.data.data }这里需要理解两个知识点。第一个axios.getApiResponseUser[]()其中的泛型参数用于声明期望的响应体类型。因此res.data // ApiResponseUser[] res.data.data // User[]第二个PromiseUser[]因为函数使用了async所以一定返回 Promise。PromiseUser[]表示这个 Promise 成功完成后会得到一个User[]类型的结果。因此调用时const users await getUserList()TypeScript 就能推断出users是用户数组并提供相应的属性提示。不过要注意类型声明并不会真正检查服务器返回的数据。如果后端响应与声明不一致运行时依然可能出现问题需要时应结合数据校验。三、React 中的 TypeScript在 React 项目中TypeScript 最常见的使用场景是组件 Props、useState和事件处理。包含 JSX 的 TypeScript 文件通常使用.tsx后缀。3.1 为组件 Props 定义类型先看普通 React 组件function UserCard({ name, age }) { return div{name} - {age}/div }我们可以使用interface定义 Propsinterface UserCardProps { name: string age: number } function UserCard({ name, age }: UserCardProps) { return div{name} - {age}/div }这里的UserCardProps约束了组件应该接收哪些属性以及每个属性的类型。因此UserCard name张三 age{20} /能够通过类型检查而UserCard name张三 age20 /会报错因为age应该是数字。对于事件回调也可以像普通函数一样定义类型interface ButtonProps { onClick: () void }表示父组件需要传入一个无参数的回调函数。3.2 useState 为什么要使用泛型我们已经知道useState用于保存 React 组件状态。const [count, setCount] useState(0)这里没有写类型是因为 TypeScript 可以通过初始值0推断出count的类型为number。但如果初始状态是null情况就不同了。假设我们需要保存接口返回的用户信息interface User { id: number name: string }由于接口请求尚未完成初始状态可能为空const [user, setUser] useStateUser | null(null)这段代码的关键是useStateUser | null(null)其中User | null是传递给useState的泛型参数表示状态既可以是User对象也可以是null。之后setUser({ id: 1, name: 张三 })就可以正常更新状态。但如果传入的对象缺少必要字段TypeScript 会给出提示。如果不指定泛型而直接写const [user, setUser] useState(null)在严格类型检查下TypeScript 会将状态推断为null无法直接将User对象赋给它。另外下面两种写法也需要区分useStateUser()表示状态类型为User | undefined初始值是undefined。useStateUser | null(null)表示状态可以是User或null并明确将初始值设为null。访问用户信息时可以通过可选链处理空值return div{user?.name ?? 暂无用户}/div如果状态是一个数组则可以写成const [users, setUsers] useStateUser[]([])这些就是 React 项目中最常见的泛型应用。3.3 React 事件类型React 中的事件对象也有对应的类型。例如输入框的 change 事件import type { ChangeEvent } from react function handleChange( e: ChangeEventHTMLInputElement ) { console.log(e.currentTarget.value) }这里ChangeEvent是 React 提供的事件类型。HTMLInputElement表示 HTML 输入框元素。两者结合描述了输入框触发的 React change 事件。对于直接写在 JSX 中的事件函数TypeScript 通常可以自动推断input onChange{(e) { console.log(e.currentTarget.value) }} /所以不必给所有事件手动添加类型。通常只有将事件函数单独抽离、且无法从上下文推断时才需要明确声明。四、Vue 3 中的 TypeScriptVue 3 的 Composition API 也支持 TypeScript。在.vue组件中只需要指定脚本语言script setup langts /script4.1 ref 的泛型在 Vue 中ref同样支持类型推断import { ref } from vue const count ref(0)TypeScript 能自动推断count.value为number。如果要保存可能为空的用户对象interface User { id: number name: string } const user refUser | null(null)这里的refUser | null(null)与 React 中的useStateUser | null(null)在类型声明思路上是类似的。区别在于Vue 的ref在 JavaScript 中通过.value读取和修改user.value { id: 1, name: 张三 }而 React 使用状态变量读取通过setUser更新。4.2 defineProps 和 defineEmitsVue 3 可以通过defineProps为组件 Props 声明类型script setup langts interface Props { title: string count?: number } const props definePropsProps() /script template div{{ props.title }}/div /template这里的Props是类型参数定义了组件接收的属性结构。count?: number表示这个属性是可选的。此外还可以通过defineEmits为组件事件添加类型script setup langts const emit defineEmits{ change: [value: number] }() function handleClick() { emit(change, 100) } /script template button clickhandleClick点击/button /template这里规定了change事件必须携带一个数字参数。如果传入字符串就会产生类型错误。对于 Vue 项目来说ref、defineProps和defineEmits是最值得优先掌握的几个 TypeScript 使用场景。五、中篇总结学完本篇我们已经从单纯编写 TypeScript 类型声明过渡到了在前端项目中实际使用类型。其中最重要的是理解泛型的作用泛型允许我们暂时不确定具体类型但仍然保持数据之间的类型关系。这一能力既可以用于封装通用函数也可以用于接口响应类型、React 状态以及 Vue 响应式数据。实际开发时可以遵循一个简单原则能自动推断的类型就交给 TypeScript需要明确约束的数据结构则主动声明。例如普通变量依赖类型推断接口数据使用interface存在空值的状态使用联合类型而通用函数和组件 API 则根据需要使用泛型。到这里我们已经能够在普通 React、Vue 项目中完成常见的类型声明工作。下一篇《两小时带你入门 TypeScript下工程配置、类型报错与高级类型》我们会继续介绍 DOM 类型与类型断言、unknown和any、常见报错、tsconfig.json以及如何逐步把 JavaScript 项目迁移为 TypeScript。最后再通过简洁的代码示例了解keyof、typeof、Partial、Pick、映射类型、条件类型和infer帮助大家理解实际项目及第三方库中常见的高级类型写法。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑