TS 用法总结

8 阅读18分钟

0 基础 TypeScript 全套教程(Vue3 实战版)

前置说明

  1. 目标:0 基础看懂 TS,所有案例全部配套 Vue3 <script setup> 真实场景
  2. 环境:Vite + Vue3 + TS(项目创建命令:npm create vite@latest my-vue-ts -- --template vue-ts
  3. 核心逻辑: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 参数),可以换成任意字母:AItemData 都行
    • 作用:捕获调用时传入的真实类型,让函数适配任意数组,同时保留类型校验
  • 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: "新任务" })
}
  1. **ref**Vue3 响应式 API,用来包装基础类型、数组、简单对象;返回一个 Ref 响应式容器,取值需要 .value
  2. <Todo[]> 泛型
  • Todo[]:代表Todo 类型的数组,数组每一项都必须符合 Todo 结构;
  • <Todo[]> 传给 ref<T> 的泛型参数 T,用来约束 ref 内部存储值的类型
  1. **([])**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 常见踩坑解决方案

  1. props 传对象丢失类型:必须用泛型 defineProps
  2. ref (null) 赋值对象报错:ref<Type | null>(null)
  3. 路由 params 类型为 string []:手动断言 as string
  4. 第三方库无类型:安装 @types/xxx,没有就写 declare module
  5. 全局 this.$xxx 无提示:扩展 ComponentCustomProperties
  6. 循环渲染数组报类型错误:给数组定义明确 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},分别实现:

  1. 全部属性可选;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 子组件

  1. props 接收 user 对象(id 数字、name 字符串、age 可选数字)
  2. emit 触发 click 事件,传递用户 id
  3. 页面展示姓名、年龄,点击按钮抛出事件

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 泛型请求)

  1. 新建 request.ts 封装通用返回结构
  2. 组件内请求用户列表,完整类型推导

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:用户管理页面整合全部知识点

需求:

  1. 表单新增用户(name、age)
  2. 列表渲染用户,编辑回填表单
  3. computed 计算总人数
  4. onMounted 模拟接口加载数据
  5. 全部类型严格约束

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>