0 基础 TypeScript 全套教程(Vue3 实战版)
前置说明
- 目标:0 基础看懂 TS,所有案例全部配套 Vue3
<script setup>真实场景 - 环境:Vite + Vue3 + TS(项目创建命令:
npm create vite@latest my-vue-ts -- --template vue-ts) - 核心逻辑:TS = JavaScript + 静态类型,解决 JS 无类型导致的线上报错
一、TS 基础入门
1. 基础类型注解
1.1 原始类型
typescript
运行
// 基础类型:string / number / boolean / null / undefined
let name: string = "张三";
let age: number = 18;
let isLogin: boolean = true;
let empty: null = null;
let unKnow: undefined = undefined;
// 错误示范(TS会直接标红)
age = "十八"; // 类型不匹配,编译报错
1.2 any /unknown(禁用 any,优先 unknown)
typescript
运行
// any:关闭类型校验,失去TS意义,尽量不用
let data: any = "123";
data = 666;
data.foo(); // TS不报错,但运行会崩
// unknown:安全的任意类型,使用前必须类型判断
let res: unknown = "hello";
if (typeof res === "string") {
console.log(res.toUpperCase());
}
1.3 数组类型
typescript
运行
// 写法1:类型[]
let arr1: number[] = [1,2,3];
// 写法2:Array<类型>(泛型写法)
let arr2: Array<string> = ["a","b"];
// 联合类型数组
let mixArr: (string | number)[] = [1, "2"];
1.4 元组 Tuple(固定长度、固定类型数组)
typescript
运行
// [姓名, 年龄, 是否会员]
let user: [string, number, boolean] = ["李四", 22, true];
user[0] = "王五";
user[1] = "20"; // 报错,第二位必须数字
1.5 联合类型 | 、交叉类型 &
typescript
运行
// 联合:多种类型任选其一
let id: string | number = 1001;
id = "1002";
// 交叉:合并多个类型,同时拥有所有属性
type A = { name: string }
type B = { age: number }
type C = A & B;
const person: C = { name: "小明", age: 16 };
1.6 字面量类型(限定固定值)
typescript
运行
// 只能赋值这三个值
type Status = "success" | "fail" | "loading";
let reqStatus: Status = "loading";
reqStatus = "done"; // 报错,不在字面量范围内
2. 函数类型(Vue 高频使用)
2.1 参数 + 返回值注解
// 基础函数
function add(a: number, b: number): number {
return a + b;
}
Ts说明
`(a: number, b: number)`
- `a: number`:参数 `a` 类型限定为数字
- `b: number`:参数 `b` 类型限定为数字TS 会强制调用时只能传数字,传字符串 / 布尔值等直接编译报错。
`: number`(函数返回值类型注解)声明函数执行完毕后返回值一定是数字。
// 无返回值 void
function logMsg(msg: string): void {
console.log(msg);
}
`void` 表示:**函数没有有效返回值**。
- 函数内部没有 `return`;
- 或者写了 `return;`(无返回内容)
等价于:
function logMsg(msg: string): void {
console.log(msg);
return; // 空return,依然是void
}
// 可选参数 ?
function sayHi(name?: string): void {
console.log(name || "访客");
}
// 默认参数
function calc(x: number = 0): number {
return x * 2;
}
2.2 函数类型别名(Vue 回调常用)
// 定义函数类型
type HandleClick = (id: number) => void;
// 使用
const handleTabClick: HandleClick = (tabId) => {
console.log("点击tab", tabId);
};
3. 接口 Interface(Vue 定义对象标准,最常用)
约束对象的结构、属性类型、可选 / 只读
// 用户接口
interface User {
readonly id: number; // 只读,不可修改
name: string;
age?: number; // 可选属性
// 方法
sayHello: () => void;
}
// 实现接口
const u: User = {
id: 1,
name: "小红",
sayHello() {
console.log("你好");
}
};
u.id = 2; // 报错,readonly只读
接口扩展 extends
interface User {
name: string
}
interface VipUser extends User {
vipLevel: number
}
const vip: VipUser = { name: "老王", vipLevel: 3 };
4. 类型别名 Type
// 基础对象别名
type Goods = {
id: number;
title: string;
}
// type支持联合、interface不支持
type Id = string | number;
4.1 type个interface区别
| 对比维度 | interface(接口) | type(类型别名) |
|---|---|---|
| 核心用途 | 描述对象 / 类结构,支持多次合并、继承扩展 | 任意类型别名:联合、交叉、字面量、元组、原始类型、复杂复合类型 |
| 重复定义 | 同名自动合并(声明合并) | 同名直接报错,不能重复定义 |
| 扩展方式 | extends 继承 | & 交叉类型合并 |
| 基础类型 | 不能直接给 string/number 起别名 | 可以:type Num = number |
| 联合 / 元组 | 不支持 | 完全支持 |
| 类实现 | class A implements I 只能用 interface | 不能直接 implements type |
| Vue 业务推荐 | 数据模型、组件 Props 对象、类结构 | 状态字面量、接口返回泛型、联合 ID、工具复合类型 |
1. type 可以给原始类型、联合、元组起别名;interface 不行
// 1. 原始类型别名
type Str = string
type Num = number
// 2. 联合类型
type Uid = string | number
// 3. 元组
type Point = [number, number]
// 4. 字面量联合(订单状态)
type OrderStatus = "pending" | "paid" | "refund"
// 使用
const id: Uid = 1001
const pos: Point = [10, 20]
const status: OrderStatus = "paid"
interface 做不到,但 type 可以的例子
原始类型别名(interface 报错)
// type 合法
type Num = number
const n: Num = 10
// interface 非法,直接报错
interface Num extends number {}
联合类型别名(interface 不行)
// type 合法
type Status = "pending" | "paid" | "refund"
// interface 不能直接包裹联合
interface Status extends "pending" | "paid" {} // 报错
元组别名(interface 不行)
// type 合法
type Point = [number, number]
const p: Point = [10, 20]
// interface 无法直接定义元组
interface Point { 0: number; 1: number } // 写法别扭,不推荐,不是原生元组
交叉类型
type A = { id: number }
type B = { name: string }
type C = A & B // type支持交叉
interface C extends A, B {} // interface只能用extends继承,不能直接写 &
简单函数别名
type Fn = (a: number) => string // type一行搞定
// interface 只能用对象函数形式,啰嗦
interface Fn {
(a: number): string
}
2. interface 支持声明合并,重复同名自动合并;type 重复直接报错
interface User {
id: number
}
// 同名interface自动合并属性
interface User {
name: string
}
// 同时拥有 id + name
const u: User = { id: 1, name: "小明" }
type 重复定义直接报错
type User = { id: number }
// 报错:标识符“User”重复
type User = { name: string }
3. 扩展方式不同
interface 用 extends;
interface User {
name: string
}
// 继承原有属性 + 新增
interface VipUser extends User {
vipLevel: number
}
const vip: VipUser = { name: "老王", vipLevel: 3 }
type 用 & 交叉类型
type User = { name: string }
type VipUser = User & { vipLevel: number }
const vip: VipUser = { name: "老王", vipLevel: 3 }
4. class 类只能 implements interface,不能 implements type
interface IUser {
name: string
say(): void
}
// 合法:类实现接口
class User implements IUser {
name = "张三"
say() {}
}
type TUser = {
name: string
say(): void
}
// 报错:implements 后面不能写type
class User2 implements TUser {}
5. interface 只能描述对象结构;type 可以任意组合复杂类型
type 写法
// 泛型通用返回
type ApiRes<T> = {
code: number
data: T
msg: string
}
// 用户数据
type UserItem = { id: number; name: string }
// 接口返回:联合+泛型+对象复合
type UserResponse = ApiRes<UserItem[]> | ApiRes<null>
interface写法,后面一句行不能这么写
interface ApiRes<T> { code: number data: T msg: string }
interface UserItem { id: number name: string }
5. 泛型 Generics(TS 核心难点,Vue 组件 / 请求必用)
泛型:类型参数,一套代码适配多种类型,不丢失类型提示
5.1 基础泛型函数
// T 代表任意传入类型
function getFirst<T>(arr: T[]): T {
return arr[0];
}
const num = getFirst([1,2,3]); // 自动推导 T=number
const str = getFirst(["a","b"]); // T=string
-
<T>:声明泛型类型参数T只是约定俗成的名字(Type/Type 参数),可以换成任意字母:A、Item、Data都行- 作用:捕获调用时传入的真实类型,让函数适配任意数组,同时保留类型校验
-
arr: T[]:参数是T 类型的数组 -
返回值
: T:函数返回值类型和数组元素类型完全一致
T 的运行逻辑(分两步)
步骤 1:定义函数时,T 是「占位符」
写函数的时候不知道使用者会传什么数组:数字数组、字符串数组、对象数组……用 T 临时代替未知类型,不绑定具体类型。
步骤 2:调用函数时,T 被「自动推导 / 手动指定」成真实类型
示例 1:自动推导(最常用)
// 传入 number[],TS 自动把 T = number
const num = getFirst([1,2,3]);
// num 类型:number
// 传入 string[],T = string
const str = getFirst(["a","b"]);
// str 类型:string
示例 2:手动显式指定 T
// 强制 T = boolean
const flag = getFirst<boolean>([true, false]);
5.2 泛型约束 extends(限制传入类型)
// 先定义带 length 属性的接口
interface HasLength {
length: number
}
// 约束T必须包含length属性
function getLen<T extends HasLength>(target: T): number {
return target.length;
}
getLen("abc");
getLen([1,2]);
getLen(123); // 数字无length,报错
-
<T>泛型参数T还是类型占位符,代表传入参数的类型。 -
extends HasLength核心:泛型约束语法:
泛型参数 extends 类型作用:限制 T 必须满足HasLength的结构,也就是:传入的参数必须拥有length: number属性。不满足的类型直接编译报错。 -
参数
target: T传入的值是符合约束的 T 类型。
-
返回
number返回它身上的 length 属性。
getLen("abc"); // string ✅ length=3
getLen([1,2,3]); // array ✅ length=3
getLen({ length: 10 }); // 自定义对象 ✅
getLen(123); // number ❌ 没有length,TS报错
getLen(null); // ❌
getLen(undefined); // ❌
getLen({}); // ❌ 缺少length
5.3 泛型接口(后端返回数据通用结构)
// 后端统一返回格式 { code, data, msg }
interface ApiRes<T> {
code: number;
data: T;
msg: string;
}
// 用户数据类型
type UserItem = { id: number; name: string }
// 模拟接口返回
const userApi: ApiRes<UserItem[]> = {
code: 200,
data: [{ id: 1, name: "张三" }],
msg: "成功"
}
6. 枚举 Enum(状态、下拉选项)
// 订单状态枚举
enum OrderStatus {
PENDING = 1,
PAID = 2,
DELIVERED = 3
}
const status: OrderStatus = OrderStatus.PENDING;
console.log(OrderStatus[2]); // "PAID" 反向映射
7. 类型断言 as / ! 非空断言
// as 强制指定类型
const dom = document.getElementById("app") as HTMLDivElement;
dom.style.color = "#fff";
// ! 非空断言:告诉TS该值一定不为null/undefined
const input = document.querySelector("input")!;
input.value = "";
8. 工具泛型(内置,Vue 项目高频)
Partial 全部属性可选
type User = { id: number; name: string }
// 内置工具类型 Partial
type UserPartial = Partial<User>
//等价展开: { id?: number | undefined; name?: string | undefined }
const editForm: UserPartial = { name: "临时修改" };
-
Partial<T>内置泛型工具作用:将传入类型
T的所有属性全部变为可选属性语法:Partial<目标类型> -
?= 属性可选,可以不传; -
属性类型自动带上
| undefined。
Required 全部属性必填
type User = { id: number; name: string }
type UserPartial = Partial<User>
// UserPartial = { id?: number | undefined; name?: string | undefined }
type UserReq = Required<UserPartial>
//等价于 type UserReq = { id: number; name: string; }
Readonly 全部只读
type ReadUser = Readonly<User>
const user: User = { id: 1, name: "张三" };
user.name = "李四"; // ✅ 普通对象可修改属性
const readUser: ReadUser = { id: 1, name: "张三" };
readUser.name = "李四"; // ❌ 报错:name 是只读属性,无法修改
readUser.id = 2; // ❌ id 只读
关键注意点:浅只读
Readonly 仅作用于第一层属性,嵌套对象内部依然可改:
type Info = {
id: number
info: { age: number }
}
type ReadInfo = Readonly<Info>
const data: ReadInfo = { id: 1, info: { age: 18 } }
data.id = 2; // ❌ 第一层只读
data.info.age = 20; // ✅ 嵌套对象不受 readonly 限制
想要深层只读需要自己封装深层映射类型。
Pick 挑选部分属性
type UserName = Pick<User, "name">
Omit 剔除部分属性
type UserNoId = Omit<User, "id">
Record 快速定义键值对象
// Record<键类型, 值类型>
type Dict = Record<string, number>
const count: Dict = { a: 1, b: 2 };
type Dict = Record<string, number>
const count: Dict = { a: 1, b: 99 };
count.c = 10; // ✅
count.d = "abc"; // ❌ 值必须是 number
二、Vue3 + TS 核心实战(script setup 完整案例)
1. 定义 Props 类型(Vue3 TS 最基础)
方式 1:defineProps + 泛型(推荐,简洁)
<!-- UserCard.vue -->
<template>
<div>
<p>{{ user.name }}</p>
<p>年龄:{{ user.age }}</p>
<button @click="handleClick(user.id)">查看详情</button>
</div>
</template>
<script setup lang="ts">
// 定义用户类型
interface User {
id: number
name: string
age?: number
}
// 声明props类型,自带类型校验
const props = defineProps<{
user: User
}>()
// 如果不是TS写法
const props = defineProps({ user: { type: Object, required: true }})
// 子组件触发父组件事件
const emit = defineEmits<{
(e: "click", id: number): void
}>()
const handleClick = (uid: number) => {
emit("click", uid)
}
</script>
方式 2:带默认值 withDefaults
//TS 写法
const props = withDefaults(defineProps<{
title: string
show?: boolean
}>(), {
show: false,
title: "默认标题"
})
// 不是TS写法
const props = defineProps({
title: {
type: String,
default: "默认标题"
},
show: {
type: Boolean,
default: false
}
})
2. Ref / Reactive 类型标注
2.1 ref 基础类型
import { ref, reactive } from "vue"
// 自动推导类型
const count = ref(0) // Ref<number>
count.value = 10
// 手动指定复杂类型
type Goods = { id: number; title: string }
const goods = ref<Goods | null>(null)
goods.value = { id: 1, title: "TS教程" }
2.2 reactive 对象响应式
interface LoginForm {
username: string
password: string
}
const form = reactive<LoginForm>({
username: "",
password: ""
})
// 表单提交
const submit = () => {
console.log(form.username, form.password)
}
2.3 ref 数组
type Todo = { id: number; text: string }
// 创建响应式数组
const todoList = ref<Todo[]>([])
const addTodo = () => {
todoList.value.push({ id: Date.now(), text: "新任务" })
}
- **
ref**Vue3 响应式 API,用来包装基础类型、数组、简单对象;返回一个Ref响应式容器,取值需要.value。 <Todo[]>泛型
Todo[]:代表Todo 类型的数组,数组每一项都必须符合 Todo 结构;<Todo[]>传给ref<T>的泛型参数T,用来约束 ref 内部存储值的类型。
- **
([])**ref 的初始值:空数组[],TS 会校验这个空数组将来只能存放Todo对象。
3. computed 计算属性类型
import { computed, ref } from "vue"
const price = ref(100)
const count = ref(2)
// 自动推导返回类型 number
const totalPrice = computed(() => price.value * count.value)
// 手动标注复杂返回值
type UserInfo = { fullName: string }
const user = ref({ firstName: "张", lastName: "三" })
const fullUser = computed<UserInfo>(() => ({
fullName: `${user.value.firstName}${user.value.lastName}`
}))
4. 路由 useRoute /useRouter TS 类型
安装 @vue-router/auto 或手动声明路由元信息
import { useRoute, useRouter } from "vue-router"
const route = useRoute()
const router = useRouter()
// 获取路由参数(类型断言)
const id = route.params.id as string
router.push({ path: "/detail", query: { uid: 100 } })
5. Axios 请求封装(泛型 ApiRes 实战)
import axios from "axios"
// 统一后端返回结构
export interface ApiRes<T> {
code: number
data: T
msg: string
}
// 用户类型
export type UserItem = {
id: number
name: string
age: number
}
// 请求函数,泛型控制返回data类型
export function getUserList() {
return axios.get<ApiRes<UserItem[]>>("/api/user/list")
}
组件内使用:
import { getUserList, UserItem } from "@/request"
const userList = ref<UserItem[]>([])
const loadData = async () => {
const res = await getUserList()
// res.data 自动推导出 ApiRes<UserItem[]>
if (res.data.code === 200) {
userList.value = res.data.data
}
}
loadData()
6. 自定义组合式函数 useXXX TS 规范
封装 useTodo.ts
// src/composables/useTodo.ts
import { ref } from "vue"
export type Todo = {
id: number
text: string
done: boolean
}
export function useTodo() {
const list = ref<Todo[]>([])
const add = (text: string) => {
list.value.push({
id: Date.now(),
text,
done: false
})
}
const toggle = (targetId: number) => {
const item = list.value.find(i => i.id === targetId)
if (item) item.done = !item.done
}
return { list, add, toggle }
}
组件调用:
vue
<script setup lang="ts">
import { useTodo } from "@/composables/useTodo"
const { list, add, toggle } = useTodo()
</script>
7. 全局属性扩展(、store 类型补全)
src/env.d.ts
import { ComponentCustomProperties } from "vue"
declare module "vue" {
interface ComponentCustomProperties {
$message: (msg: string) => void
}
}
组件内 $message("提示") 不会报类型错误
三、TS 进阶深度(大型 Vue 项目必备)
1. 类型收窄(Narrowing)
TS 自动缩小类型范围,减少断言
type Res = { code: number } | { err: string }
const apiRes: Res = { code: 200 }
if ("code" in apiRes) {
console.log(apiRes.code) // TS识别为成功结构
} else {
console.log(apiRes.err)
}
2. 函数重载(Vue 复杂工具函数)
同一个函数多套参数类型
// 重载签名
function formatTime(timestamp: number): string
function formatTime(year: number, month: number): string
// 实现
function formatTime(a: number, b?: number): string {
if (!b) return new Date(a).toLocaleString()
return `${a}-${b}`
}
formatTime(1700000000000)
formatTime(2026, 7)
3. 索引签名、映射类型
索引签名(动态对象 key)
interface CacheObj {
[key: string]: number | string
}
const cache: CacheObj = { a: 1, b: "2" }
映射类型(批量改造已有类型)
type User = { id: number; name: string }
// 全部属性转为只读
type ReadAll<T> = {
readonly [K in keyof T]: T[K]
}
type ReadUser = ReadAll<User>
4. 类 Class + TS(封装工具类)
class StorageUtil {
prefix: string
constructor(prefix: string = "vue_ts_") {
this.prefix = prefix
}
set<T>(key: string, value: T): void {
localStorage.setItem(this.prefix + key, JSON.stringify(value))
}
get<T>(key: string): T | null {
const str = localStorage.getItem(this.prefix + key)
return str ? JSON.parse(str) : null
}
}
// 使用
const storage = new StorageUtil()
storage.set<User[]>("user", [{ id: 1, name: "测试" }])
const user = storage.get<User[]>("user")
5. 模块声明(导入图片 / 资源无类型报错)
env.d.ts
declare module "*.png"
declare module "*.svg"
declare module "*.vue"
四、Vue3 TS 常见踩坑解决方案
- props 传对象丢失类型:必须用泛型 defineProps
- ref (null) 赋值对象报错:
ref<Type | null>(null) - 路由 params 类型为 string []:手动断言
as string - 第三方库无类型:安装
@types/xxx,没有就写 declare module - 全局 this.$xxx 无提示:扩展 ComponentCustomProperties
- 循环渲染数组报类型错误:给数组定义明确 Item 类型
五、完整可运行页面示例(整合全部知识点)
vue
<template>
<div class="user-page">
<div v-for="item in userList" :key="item.id">
{{ item.name }} - {{ item.age }}
<button @click="handleEdit(item.id)">编辑</button>
</div>
<form @submit.prevent="submitForm">
<input v-model="form.name" placeholder="姓名" />
<input v-model.number="form.age" placeholder="年龄" />
<button type="submit">新增用户</button>
</form>
<p>总人数:{{ totalCount }}</p>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from "vue"
// 类型定义
export interface User {
id: number
name: string
age: number
}
// 表单
const form = reactive<Pick<User, "name" | "age">>({
name: "",
age: 0
})
// 用户列表
const userList = ref<User[]>([])
// 计算属性
const totalCount = computed(() => userList.value.length)
// 加载数据
const loadUsers = async () => {
// 模拟接口
const mockData: User[] = [
{ id: 1, name: "张三", age: 20 },
{ id: 2, name: "李四", age: 22 }
]
userList.value = mockData
}
// 新增
const submitForm = () => {
const newUser: User = {
id: Date.now(),
name: form.name,
age: form.age
}
userList.value.push(newUser)
form.name = ""
form.age = 0
}
// 编辑
const handleEdit = (uid: number) => {
const target = userList.value.find(u => u.id === uid)
if (!target) return
form.name = target.name
form.age = target.age
}
onMounted(() => loadUsers())
</script>
第一阶段:TS 基础入门题库(无 Vue,纯 TS)
模块 1:基础类型注解
题 1
需求:声明 5 个变量,分别为字符串、数字、布尔、null、undefined,标注类型并赋值。
ts
// 参考答案
let username: string = "小明";
let count: number = 99;
let isShow: boolean = false;
let empty: null = null;
let und: undefined = undefined;
知识点:原始类型基础注解
题 2
需求:定义两个数组,一个纯数字数组,一个字符串 / 数字混合数组。
ts
let numArr: number[] = [1, 2, 3];
let mixArr: (string | number)[] = [10, "20"];
知识点:数组、联合类型 |
题 3
需求:元组类型 [商品id, 商品名称, 是否上架],赋值并修改名称。
ts
type GoodsTuple = [number, string, boolean];
const goods: GoodsTuple = [1001, "TS教程", true];
goods[1] = "Vue3+TS实战";
知识点:Tuple 元组固定长度类型
题 4
需求:字面量类型,定义订单状态仅允许 pending / paid / finish。
ts
type OrderStatus = "pending" | "paid" | "finish";
let status: OrderStatus = "pending";
// status = "cancel" // 报错,超出字面量范围
知识点:字面量联合类型
模块 2:函数类型
题 5
需求:写求和函数,两个数字参数,返回数字;无参打印函数无返回值。
ts
function sum(a: number, b: number): number {
return a + b;
}
function printLog(msg: string): void {
console.log(msg);
}
知识点:函数参数、返回值、void
题 6
需求:函数支持可选参数,传入姓名打印欢迎语,不传则输出 “访客”。
ts
function welcome(name?: string): void {
console.log(`欢迎${name ?? "访客"}`);
}
welcome();
welcome("小红");
知识点:可选参数 ?
题 7
需求:定义函数类型别名 HandleClick,接收 id 数字无返回值,实现并调用。
ts
type HandleClick = (id: number) => void;
const clickFn: HandleClick = (id) => {
console.log("点击id", id);
};
clickFn(100);
知识点:函数类型别名
模块 3:Interface / Type
题 8
需求:定义 User 接口,包含只读 id、必填 name、可选 age、方法 sayHi。
ts
interface User {
readonly id: number;
name: string;
age?: number;
sayHi: () => void;
}
const u: User = {
id: 1,
name: "小李",
sayHi() {
console.log("hi");
},
};
// u.id = 2 // 报错 readonly
知识点:interface、readonly、可选属性、对象方法
题 9
需求:接口继承,VipUser 继承 User,新增 vipLevel 数字属性。
ts
interface User {
name: string;
}
interface VipUser extends User {
vipLevel: number;
}
const vip: VipUser = { name: "老王", vipLevel: 3 };
知识点:interface extends 扩展
题 10
需求:type 实现联合 Id 类型(string | number),同时定义商品对象类型。
ts
type Id = string | number;
type Goods = {
id: Id;
title: string;
};
const item: Goods = { id: "10086", title: "键盘" };
知识点:type 联合、对象类型
模块 4:泛型基础
题 11
需求:泛型函数 getArrFirst,接收任意类型数组,返回数组第一项。
ts
function getArrFirst<T>(arr: T[]): T {
return arr[0];
}
const n = getArrFirst([1, 2, 3]);
const s = getArrFirst(["a", "b"]);
知识点:基础泛型 <T>
题 12
需求:通用后端返回泛型接口 ApiRes,包含 code、data、msg,模拟用户列表返回。
ts
interface ApiRes<T> {
code: number;
data: T;
msg: string;
}
type User = { id: number; name: string };
const res: ApiRes<User[]> = {
code: 200,
data: [{ id: 1, name: "张三" }],
msg: "成功",
};
知识点:泛型接口,后端接口通用结构
模块 5:内置工具类型
题 13
给定 type User={id:number,name:string},分别实现:
- 全部属性可选;2. 只保留 name;3. 剔除 id;4. 全部只读
ts
type User = { id: number; name: string };
type UserPartial = Partial<User>;
type UserName = Pick<User, "name">;
type UserNoId = Omit<User, "id">;
type UserRead = Readonly<User>;
知识点:Partial / Pick / Omit / Readonly
题 14
需求:Record 生成键为 string、值为数字的统计对象。
ts
type CountMap = Record<string, number>;
const count: CountMap = { apple: 10, banana: 20 };
知识点:Record 键值映射
模块 6:类型断言 & 枚举
题 15
需求:获取 dom 元素,使用 as 断言为 HTMLDivElement,修改样式。
ts
const box = document.getElementById("box") as HTMLDivElement;
box.style.fontSize = "16px";
知识点:as 类型断言
题 16
需求:枚举 OrderStatus,待付款 1、已付款 2、已发货 3,赋值并打印枚举值。
ts
enum OrderStatus {
PENDING = 1,
PAID = 2,
DELIVERED = 3,
}
const s: OrderStatus = OrderStatus.PAID;
console.log(OrderStatus[3]); // DELIVERED
知识点:数字枚举、反向映射
第二阶段:TS 进阶深度题库(中大型项目必备)
模块 1:类型收窄、索引签名
题 17
需求:联合类型 {code:number} | {err:string},通过 in 判断区分成功 / 失败结构。
ts
type Res = { code: number } | { err: string };
const apiRes: Res = { code: 200 };
if ("code" in apiRes) {
console.log(apiRes.code);
} else {
console.log(apiRes.err);
}
知识点:类型收窄 in 判断
题 18
需求:索引签名对象,key 任意字符串,值支持 string/number。
ts
interface Cache {
[key: string]: string | number;
}
const cache: Cache = { token: "xxx", time: 178000000 };
知识点:索引签名动态对象
模块 2:函数重载
题 19
需求:formatTime 函数,两种入参:①时间戳数字;②年、月两个数字,返回格式化字符串。
ts
// 重载签名
function formatTime(timestamp: number): string;
function formatTime(year: number, month: number): string;
// 实现
function formatTime(a: number, b?: number): string {
if (!b) return new Date(a).toLocaleDateString();
return `${a}-${b}`;
}
formatTime(1780000000000);
formatTime(2026, 7);
知识点:函数重载
模块 3:映射类型、泛型约束
题 20
需求:自定义映射类型 ReadAll,将传入类型所有属性变为只读。
ts
type User = { id: number; name: string };
type ReadAll<T> = {
readonly [K in keyof T]: T[K];
};
type ReadUser = ReadAll<User>;
知识点:keyof、in 映射类型
题 21
需求:泛型约束,函数获取传入数据长度,约束 T 必须包含 length 属性。
ts
interface LengthItem {
length: number;
}
function getLength<T extends LengthItem>(target: T): number {
return target.length;
}
getLength("abc");
getLength([1, 2, 3]);
// getLength(123) // 数字无length报错
知识点:泛型约束 extends
模块 4:Class 类与泛型结合
题 22
封装 LocalStorage 工具类,set 支持泛型存储任意类型,get 泛型读取。
ts
class StorageCache {
prefix: string;
constructor(prefix = "app_") {
this.prefix = prefix;
}
set<T>(key: string, value: T): void {
localStorage.setItem(this.prefix + key, JSON.stringify(value));
}
get<T>(key: string): T | null {
const str = localStorage.getItem(this.prefix + key);
return str ? JSON.parse(str) : null;
}
}
const storage = new StorageCache();
storage.set<User[]>("userList", [{ id: 1, name: "测试" }]);
const list = storage.get<User[]>("userList");
知识点:TS 类、泛型工具封装
第三阶段:Vue3 + TS 实战题库(script setup 完整可运行)
模块 1:Props & Emits 类型定义
实战题 1(基础组件传参)
需求:封装 UserCard 子组件
- props 接收 user 对象(id 数字、name 字符串、age 可选数字)
- emit 触发 click 事件,传递用户 id
- 页面展示姓名、年龄,点击按钮抛出事件
vue
<!-- UserCard.vue -->
<template>
<div class="card">
<h3>{{ user.name }}</h3>
<p>年龄:{{ user.age ?? "未知" }}</p>
<button @click="handleClick">查看详情</button>
</div>
</template>
<script setup lang="ts">
interface User {
id: number;
name: string;
age?: number;
}
// props类型
const props = defineProps<{
user: User;
}>();
// emit类型
const emit = defineEmits<{
(e: "click", uid: number): void;
}>();
const handleClick = () => {
emit("click", props.user.id);
};
</script>
知识点:defineProps 泛型、defineEmits 类型约束
实战题 2(props 默认值 withDefaults)
需求:弹窗组件 props:title 字符串、show 布尔可选,默认 show=false,title="提示"
vue
<script setup lang="ts">
const props = withDefaults(
defineProps<{
title: string;
show?: boolean;
}>(),
{
show: false,
title: "提示",
}
);
</script>
知识点:withDefaults 给可选 props 赋默认值
模块 2:ref /reactive/computed 类型标注
实战题 3(表单响应式对象)
需求:登录表单,username、password 双向绑定,提交打印表单。
vue
<template>
<form @submit.prevent="submit">
<input v-model="form.username" placeholder="账号" />
<input v-model="form.password" placeholder="密码" />
<button>登录</button>
</form>
</template>
<script setup lang="ts">
import { reactive } from "vue";
interface LoginForm {
username: string;
password: string;
}
const form = reactive<LoginForm>({
username: "",
password: "",
});
const submit = () => {
console.log(form);
};
</script>
实战题 4(ref 复杂类型 + 计算属性)
需求:todo 列表,每条包含 id、text、done;计算属性统计已完成数量。
vue
<template>
<div v-for="item in todoList" :key="item.id">
<input v-model="item.done" type="checkbox" />
<span>{{ item.text }}</span>
</div>
<p>已完成:{{ doneCount }}</p>
</template>
<script setup lang="ts">
import { ref, computed } from "vue";
type Todo = {
id: number;
text: string;
done: boolean;
};
const todoList = ref<Todo[]>([]);
const doneCount = computed(() => todoList.value.filter((i) => i.done).length);
// 添加todo
const addTodo = (text: string) => {
todoList.value.push({ id: Date.now(), text, done: false });
};
addTodo("学习TS");
</script>
模块 3:接口请求泛型封装实战
实战题 5(axios ApiRes 泛型请求)
- 新建 request.ts 封装通用返回结构
- 组件内请求用户列表,完整类型推导
ts
// src/request.ts
import axios from "axios";
export interface ApiRes<T> {
code: number;
data: T;
msg: string;
}
export type User = {
id: number;
name: string;
age: number;
};
export function getUserList() {
return axios.get<ApiRes<User[]>>("/api/user/list");
}
组件使用:
vue
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { getUserList, User } from "@/request";
const userList = ref<User[]>([]);
const loadData = async () => {
const res = await getUserList();
if (res.data.code === 200) {
userList.value = res.data.data;
}
};
onMounted(loadData);
</script>
模块 4:组合式函数 useXXX TS 规范
实战题 6(封装 useTodo 组合函数)
新建 composables/useTodo.ts
ts
import { ref } from "vue";
export type Todo = {
id: number;
text: string;
done: boolean;
};
export function useTodo() {
const list = ref<Todo[]>([]);
const add = (text: string) => {
list.value.push({ id: Date.now(), text, done: false });
};
const toggle = (targetId: number) => {
const item = list.value.find((i) => i.id === targetId);
if (item) item.done = !item.done;
};
return { list, add, toggle };
}
组件调用:
vue
<script setup lang="ts">
import { useTodo } from "@/composables/useTodo";
const { list, add, toggle } = useTodo();
</script>
模块 5:综合完整页面大题(期末综合题)
实战大题 7:用户管理页面整合全部知识点
需求:
- 表单新增用户(name、age)
- 列表渲染用户,编辑回填表单
- computed 计算总人数
- onMounted 模拟接口加载数据
- 全部类型严格约束
vue
<template>
<div>
<div v-for="item in userList" :key="item.id">
{{ item.name }} - {{ item.age }}
<button @click="handleEdit(item.id)">编辑</button>
</div>
<form @submit.prevent="submitForm">
<input v-model="form.name" />
<input v-model.number="form.age" />
<button type="submit">保存</button>
</form>
<p>总用户:{{ totalCount }}</p>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from "vue";
export interface User {
id: number;
name: string;
age: number;
}
// 表单 只取name、age
const form = reactive<Pick<User, "name" | "age">>({
name: "",
age: 0,
});
const userList = ref<User[]>([]);
const totalCount = computed(() => userList.value.length);
// 加载模拟数据
const loadUsers = () => {
userList.value = [
{ id: 1, name: "张三", age: 20 },
{ id: 2, name: "李四", age: 22 },
];
};
// 新增
const submitForm = () => {
userList.value.push({
id: Date.now(),
name: form.name,
age: form.age,
});
form.name = "";
form.age = 0;
};
// 编辑回填
const handleEdit = (uid: number) => {
const target = userList.value.find((u) => u.id === uid);
if (!target) return;
form.name = target.name;
form.age = target.age;
};
onMounted(loadUsers);
</script>