找回密码
 立即注册
首页 业界区 业界 前端 TypeScript 入门2

前端 TypeScript 入门2

怒鼓踊 1 小时前
前端 TypeScript 入门2

在上一篇中,我们了解了 TS 常用语法,但是在Vue3项目实际开发中,会发现很多 TS 代码看不懂。本篇以实际 Vue3 项目为例,抽取出其中绝大多数 TS 常见写法,快速进入实战。
一、API 层的 TypeScript 用法

1.1 定义接口数据结构(interface)

在项目中,我们使用 interface 定义后端返回的数据结构。
  1. // src/api/system/user/profile.ts
  2. export interface ProfileVO {
  3.   id: number
  4.   username: string
  5.   nickname: string
  6.   dept: {
  7.     id: number
  8.     name: string
  9.   }
  10.   // roles 是一个数组,数组里的每一项都是包含 id 和 name 两个字段的对象
  11.   roles: {
  12.     id: number
  13.     name: string
  14.   }[]
  15.   posts: {
  16.     id: number
  17.     name: string
  18.   }[]
  19.   email: string
  20.   sex: number
  21.   status: number
  22.   remark: string
  23.   createTime: Date
  24. }
复制代码
关键点:

  • [] 表示数组类型,如 roles: {...}[] 表示角色数组
  • 嵌套对象直接在 interface 中定义,如 dept: { id: number; name: string }
  • Date 类型表示日期时间
1.2 可选属性与联合类型
  1. // src/api/system/user/profile.ts
  2. export interface UserProfileUpdateReqVO {
  3.   nickname?: string // ? 表示可选属性
  4.   email?: string
  5.   mobile?: string
  6.   sex?: number
  7.   avatar?: string
  8. }
  9. // src/api/system/sms/smsLog/index.ts
  10. export interface SmsLogVO {
  11.   id: number | null // 联合类型:可以是 number 或 null
  12.   channelId: number | null
  13.   templateParams: Map<string, object> | null
  14.   sendStatus: number | null
  15.   sendTime: Date | null
  16. }
复制代码
关键点:

  • ? 表示该属性可选,可以不传
  • | 表示联合类型,值可以是多种类型之一
  • null 和 undefined 常用于表示空值
  • Map: 键是 string、值是 object 的 Map
1.3 接口继承(extends)
  1. // src/api/system/tenant/index.ts
  2. export interface TenantPageReqVO extends PageParam {
  3.   name?: string
  4.   contactName?: string
  5.   contactMobile?: string
  6.   status?: number
  7.   createTime?: Date[]
  8. }
复制代码
关键点:

  • extends PageParam 继承了分页参数(pageNo, pageSize)
  • 继承后可以添加自己的属性
  • PageParam 是全局类型,定义在 types/global.d.ts
  1. // global.d.ts
  2. export {}
  3. declare global {
  4.   interface PageParam {
  5.     pageSize?: number
  6.     pageNo?: number
  7.   }
  8. }
复制代码
TypeScript 是如何找到这个 global.d.ts 的:
  1. {
  2.   "compilerOptions": {
  3.     // 告诉 TS:类型声明文件的根目录在这两个地方:
  4.     "typeRoots": ["./node_modules/@types/", "./types"]
  5.   },
  6.   // 参与编译/类型检查的文件包括 types 目录下所有 .d.ts
  7.   "include": [
  8.     "src",
  9.     "types/**/*.d.ts",
  10.     "src/types/auto-imports.d.ts",
  11.     "src/types/auto-components.d.ts"
  12.   ]
  13. }
复制代码
1.4 API 函数的类型标注
  1. // src/api/system/user/index.ts
  2. // 查询用户详情 - 参数和返回值都有类型
  3. export const getUser = (id: number) => {
  4.   return request.get({ url: '/system/user/get?id=' + id })
  5. }
  6. // 新增用户 - data 参数类型为 UserVO
  7. export const createUser = (data: UserVO) => {
  8.   return request.post({ url: '/system/user/create', data })
  9. }
  10. // 修改用户
  11. export const updateUser = (data: UserVO) => {
  12.   return request.put({ url: '/system/user/update', data })
  13. }
  14. // 批量删除 - 参数是数字数组
  15. export const deleteUserList = (ids: number[]) => {
  16.   return request.delete({ url: '/system/user/delete-list', params: { ids: ids.join(',') } })
  17. }
复制代码
关键点:

  • 参数类型写在参数名后面:(id: number)
  • 对象类型参数用 interface:(data: UserVO)
  • 数组类型:ids: number[]
1.5 async/await 与 Promise 类型
  1. // src/api/system/post/index.ts
  2. // 异步返回一个 Promise,这个 Promise 最终会 resolve 成 PostVO 对象组成的数组。
  3. // 返回 Promise<PostVO[]>。
  4. export const getSimplePostList = async (): Promise<PostVO[]> => {
  5.   return await request.get({ url: '/system/post/simple-list' })
  6. }
  7. // 查询详情
  8. export const getPost = async (id: number) => {
  9.   return await request.get({ url: '/system/post/get?id=' + id })
  10. }
  11. // 删除
  12. export const deletePost = async (id: number) => {
  13.   return await request.delete({ url: '/system/post/delete?id=' + id })
  14. }
复制代码
关键点:

  • async 函数自动返回 Promise
  • Promise 明确返回的数据类型
  • await 等待异步操作完成
二、Store(Pinia)层的 TypeScript 用法

2.1 定义 Store 的 State 类型
  1. // src/store/modules/user.ts
  2. export interface CompanyVO {
  3.   pid: string
  4.   companyName: string
  5.   isDefault: number
  6. }
  7. interface UserVO {
  8.   id: number
  9.   avatar: string
  10.   nickname: string
  11.   deptId: number
  12.   companyList: CompanyVO[]
  13.   sex?: number
  14.   position?: string
  15. }
  16. interface UserInfoVO {
  17.   permissions: Set<string> // Set 类型
  18.   roles: string[]
  19.   isSetUser: boolean
  20.   user: UserVO
  21. }
  22. export const useUserStore = defineStore('admin-user', {
  23.   state: (): UserInfoVO => ({
  24.     permissions: new Set<string>(),
  25.     roles: [],
  26.     isSetUser: false,
  27.     user: {
  28.       id: 0,
  29.       avatar: '',
  30.       nickname: '',
  31.       deptId: 0,
  32.       companyList: []
  33.     }
  34.   })
  35.   // ... getters 和 actions
  36. })
复制代码
关键点:

  • state: (): UserInfoVO => (...) 定义 state 返回类型是 UserInfoVO。主要是为了类型检查和提示,不一定会被你显式“拿出来用”。
  • Set 表示字符串集合。Set 中的值必须是 string 类型。
2.2 Getters 的类型
  1. // src/store/modules/user.ts
  2. export const useUserStore = defineStore('admin-user', {
  3.   // ... state
  4.   getters: {
  5.     getPermissions(): Set<string> {
  6.       return this.permissions
  7.     },
  8.     getRoles(): string[] {
  9.       return this.roles
  10.     },
  11.     getIsSetUser(): boolean {
  12.       return this.isSetUser
  13.     },
  14.     getUser(): UserVO {
  15.       return this.user
  16.     }
  17.   }
  18. })
复制代码
关键点:

  • getter 函数后面标注返回类型
  • 使用 this 访问 state
2.3 Actions 的类型标注
  1. // src/store/modules/user.ts
  2. export const useUserStore = defineStore('admin-user', {
  3.   // ... state, getters
  4.   actions: {
  5.     async setUserInfoAction() {
  6.       if (!getAccessToken()) {
  7.         this.resetState()
  8.         return null
  9.       }
  10.       let userInfo = wsCache.get(CACHE_KEY.USER)
  11.       if (!userInfo) {
  12.         userInfo = await getInfo()
  13.       }
  14.       this.permissions = new Set(userInfo.permissions || [])
  15.       this.roles = userInfo.roles
  16.       this.user = userInfo.user
  17.     },
  18.     async setUserAvatarAction(avatar: string) {
  19.       const userInfo = wsCache.get(CACHE_KEY.USER)
  20.       this.user.avatar = avatar
  21.       userInfo.user.avatar = avatar
  22.       wsCache.set(CACHE_KEY.USER, userInfo)
  23.     },
  24.     async loginOut() {
  25.       try {
  26.         await loginOut()
  27.       } catch (error) {
  28.         console.error('登出接口调用失败:', error)
  29.       } finally {
  30.         removeToken()
  31.         deleteUserCache()
  32.         this.resetState()
  33.       }
  34.     }
  35.   }
  36. })
复制代码
关键点:

  • action 函数参数需要类型:(avatar: string)
  • async action 返回 Promise
  • 通过 this 修改 state
2.4 Map 类型的使用
  1. // src/store/modules/mall/kefu.ts
  2. interface MallKefuInfoVO {
  3.   conversationList: KeFuConversationRespVO[]
  4.   conversationMessageList: Map<number, KeFuMessageRespVO[]> // Map 类型
  5. }
  6. export const useMallKefuStore = defineStore('mall-kefu', {
  7.   state: (): MallKefuInfoVO => ({
  8.     conversationList: [],
  9.     conversationMessageList: new Map<number, KeFuMessageRespVO[]>()
  10.   }),
  11.   getters: {
  12.     // 返回函数的 getter
  13.     getConversationMessageList(): (conversationId: number) => KeFuMessageRespVO[] | undefined {
  14.       return (conversationId: number) => this.conversationMessageList.get(conversationId)
  15.     }
  16.     /*
  17.     等价:
  18.     type GetMsgFn = (conversationId: number) => KeFuMessageRespVO[] | undefined
  19.     getConversationMessageList(): GetMsgFn {
  20.       return (conversationId: number) => this.conversationMessageList.get(conversationId)
  21.     }
  22.     */
  23.   },
  24.   actions: {
  25.     saveMessageList(conversationId: number, messageList: KeFuMessageRespVO[]) {
  26.       this.conversationMessageList.set(conversationId, messageList)
  27.     }
  28.   }
  29. })
复制代码
关键点:

  • Map 键是 number,值是数组
  • getter 可以返回函数
  • 使用 .get() 和 .set() 操作 Map
  • (conversationId: number) => KeFuMessageRespVO[] | undefined: 类型是函数,返回值是KeFuMessageRespVO[]或undefined
2.5 复杂 State 定义
  1. // src/store/modules/app.ts
  2. interface AppState {
  3.   breadcrumb: boolean
  4.   breadcrumbIcon: boolean
  5.   collapse: boolean
  6.   uniqueOpened: boolean
  7.   hamburger: boolean
  8.   screenfull: boolean
  9.   size: boolean
  10.   locale: boolean
  11.   message: boolean
  12.   tagsView: boolean
  13.   tagsViewImmerse: boolean
  14.   tagsViewIcon: boolean
  15.   logo: boolean
  16.   fixedHeader: boolean
  17.   greyMode: boolean
  18.   pageLoading: boolean
  19.   layout: LayoutType
  20.   title: string
  21.   userInfo: string
  22.   isDark: boolean
  23.   currentSize: ElementPlusSize
  24.   sizeMap: ElementPlusSize[]
  25.   mobile: boolean
  26.   footer: boolean
  27.   theme: ThemeTypes
  28.   fixedMenu: boolean
  29. }
  30. export const useAppStore = defineStore('app', {
  31.   state: (): AppState => {
  32.     return {
  33.       userInfo: 'userInfo',
  34.       sizeMap: ['default', 'large', 'small'],
  35.       mobile: false,
  36.       title: import.meta.env.VITE_APP_TITLE,
  37.       pageLoading: false
  38.       // ... 其他属性
  39.     }
  40.   }
  41. })
复制代码
关键点:

  • 布尔类型属性集中定义
  • 自定义类型:LayoutType, ElementPlusSize, ThemeTypes
  • 使用 import.meta.env 获取环境变量
三、Views(Vue 组件)层的 TypeScript 用法

3.1 defineOptions 和 ref
  1. // src/views/system/role/index.vue
复制代码
关键点:

  • defineOptions 定义组件选项
  • ref 自动推断类型
  • 不需要显式标注简单类型
3.2 明确 ref 类型
  1. // src/views/system/user/index.vue
复制代码
关键点:

  • ref(初始值) 明确类型
  • 数组类型:ref([])
  • 使用导入的 API 类型
3.3 reactive 定义复杂对象
  1. // src/views/system/user/index.vue
复制代码
关键点:

  • reactive 用于复杂对象
  • 自动推断类型
  • 适合查询参数对象
3.4 defineProps 类型定义
  1. // src/views/system/user/UserForm.vue
复制代码
  1. // src/views/system/companyTree/detail.vue
复制代码
关键点:

  • defineProps() 泛型方式定义 props
  • 可选属性用 ?
  • 联合类型:{ name: string; pid: number } | null
  • computed 包装 props 进行计算
defineProps语法:
  1. // 孩子没脾气
  2. defineProps(['persons'])
  3. // 接收+限制类型
  4. defineProps<{persons:Persons}>()
  5. // 接收+限制类型+限制必要性 —— 可以不传
  6. defineProps<{persons?:Persons}>()
  7. // 接收+限制类型+限制必要性+默认值
  8. import {withDefaults} from 'vue'
  9. withDefaults(defineProps<{persons?:Persons}>(), {
  10.   persons: () => []
  11. })
复制代码
3.5 defineEmits 类型定义
  1. // src/views/system/user/CompanyTree.vue
复制代码
关键点:

  • (e: '事件名', 参数: 类型): void
  • 可以定义多个事件
  • 使用 emits('事件名', 参数) 触发
声明一个名为 'node-click'的事件的三种写法(区别是类型检查严格程度):
  1. defineEmits(['node-click'])
  2. // 定义一个名为 'node-click'​ 的事件。这是一个函数签名形式的定义
  3. (e: 'node-click', row: any): void
  4. const emit = defineEmits<{
  5.   // 新语法:'事件名': [参数1类型, 参数2类型?]
  6.   'node-click': [row: any]  
  7. }>();
复制代码
3.6 FormRules 表单验证类型
  1. // src/views/system/user/ResetPasswordForm.vue
复制代码
关键点:

  • FormRules 来自 element-plus
  • reactive 定义验证规则
  • as number | undefined 类型断言。手动告诉 TS 这个值的类型是 number | undefined(联合类型)。
  • id: undefined as number | undefined 初始值是 undefined,但后面可能会被赋值成 number,所以类型标注为 number | undefined
3.7 组件 ref 类型
  1. // src/views/system/user/index.vue
复制代码
关键点:

  • InstanceType 获取组件实例类型

    • ref():创建一个响应式引用,类型是 T
    • T 就是 InstanceType
    • typeof ElTree:获取 ElTree 组件的构造函数类型
    • InstanceType:从构造函数类型中提取实例类型

  • ?. 可选链操作符,避免 undefined 报错
3.8 computed 计算属性类型
  1. // src/views/system/user/UserForm.vue
复制代码
关键点:

  • computed(() => {}) 自动推断
  • computed(() => {}) 明确类型
四、全局类型定义(types)

4.1 全局工具类型
  1. // types/global.d.ts
  2. declare global {
  3.   // 函数类型
  4.   interface Fn<T = any> {
  5.     (...arg: T[]): T
  6.   }
  7.   // 可空类型
  8.   /*
  9.   - type Xxx = ...:和 ype Message = number | string 一样,都是起一个类型别名
  10.   - T:是一个类型参数,占位的,不是具体类型
  11.   - 示例:
  12.     - type Nullable<T> = T | null
  13.     - type A = Nullable<string>   // string | null
  14.     - type B = Nullable<number>   // number | null
  15.   */
  16.   type Nullable<T> = T | null
  17.   // 元素引用类型。声明一个“可以为 null 的 DOM 引用类型”,T 必须是 HTMLElement 的子类型,默认用 HTMLDivElement。
  18.   type ElRef<T extends HTMLElement = HTMLDivElement> = Nullable<T>
  19.   // 记录类型(对象)
  20.   /*
  21.     如果不考虑 null/undefined 的边界情况,可以简化成:
  22.     type Recordable<T = any, K = string> = Record<K, T>
  23.     - Record<K, V> 是 TypeScript 内置的工具类型
  24.       - 一个对象,键的类型是 K,值的类型是 V
  25.       - 示例:
  26.         type User = Record<string, any>
  27.         // 等价于:{ [key: string]: any }
  28.         const user: User = {
  29.           name: '张三',
  30.           age: 18,
  31.           job: '工程师'
  32.         }
  33.     - 条件类型:K extends null | undefined ? string : K
  34.       - 如果 K 是 null 或 undefined → 键类型用 string
  35.       - 否则 → 键类型就用 K 本身
  36.     - 最终结果:Record<键类型, T>
  37.   */
  38.   type Recordable<T = any, K = string> = Record<K extends null | undefined ? string : K, T>
  39.   // 组件引用类型
  40.   /*
  41.     做一层语义封装,让代码更清晰。
  42.     不用别名:
  43.       const dialogRef = ref<InstanceType<typeof ElDialog>>()
  44.       const tableRef = ref<InstanceType<typeof ElTable>>()
  45.     用别名(用 ComponentRef):
  46.       const dialogRef = ref<ComponentRef<typeof ElDialog>>()
  47.       const tableRef = ref<ComponentRef<typeof ElTable>>()
  48.   */
  49.   type ComponentRef<T> = InstanceType<T>
  50.   // 分页参数
  51.   interface PageParam {
  52.     pageSize?: number
  53.     pageNo?: number
  54.   }
  55.   // 分页结果
  56.   interface PageResult<T> {
  57.     list: T
  58.     total: number
  59.   }
  60.   // 树结构
  61.   interface Tree {
  62.     id: number
  63.     name: string
  64.     children?: Tree[] | any[]
  65.   }
  66. }
复制代码
关键点:

  • declare global 声明全局类型
  • 泛型类型:Nullable, PageResult
  • 在任何文件中都可以直接使用
4.2 表单 Schema 类型
  1. // src/types/form.d.ts
  2. export type FormValueType = string | number | string[] | number[] | boolean | undefined | null
  3. export type FormItemProps = {
  4.   labelWidth?: string | number
  5.   required?: boolean
  6.   rules?: Recordable
  7.   error?: string
  8.   showMessage?: boolean
  9.   inlineMessage?: boolean
  10.   style?: CSSProperties
  11. }
  12. export type FormSchema = {
  13.   field: string
  14.   label?: string
  15.   labelMessage?: string
  16.   colProps?: ColProps
  17.   /*
  18.     定义一个可选属性 componentProps,它的类型是两个类型的交叉
  19.       - componentProps?:可选属性
  20.       - { slots?: Recordable } & ComponentProps:交叉类型(&)
  21.       - 交叉示例:
  22.         type A = { name: string }
  23.         type B = { age: number }
  24.         type C = A & B  // { name: string; age: number }
  25.         const obj: C = {
  26.           name: '张三',
  27.           age: 18
  28.         }
  29.       - { slots?: Recordable } & ComponentProps
  30.         - 必须包含 ComponentProps 里的所有属性
  31.         - 同时可以有一个可选的 slots 属性,类型是 Recordable(也就是 Record<string, any>)
  32.   */
  33.   componentProps?: { slots?: Recordable } & ComponentProps
  34.   formItemProps?: FormItemProps
  35.   component?: ComponentName
  36.   value?: FormValueType
  37.   hidden?: boolean
  38.   // 返回值是 AxiosPromise<T>(也就是 Promise>),resolve 的值是 AxiosResponse<T>,而 response.data 的类型是 T。
  39.   api?: <T = any>() => AxiosPromise<T>
  40. }
复制代码
关键点:

  • type 定义类型别名
  • 联合类型:string | number | boolean
  • 泛型函数:() => AxiosPromise
五、实用工具函数的 TypeScript

5.1 函数参数和返回值类型
  1. // src/utils/index.ts
  2. // 字符串转换
  3. export const humpToUnderline = (str: string): string => {
  4.   return str.replace(/([A-Z])/g, '-$1').toLowerCase()
  5. }
  6. // 设置 CSS 变量
  7. export const setCssVar = (prop: string, val: any, dom = document.documentElement) => {
  8.   dom.style.setProperty(prop, val)
  9. }
  10. // 数组查找(泛型函数)
  11. export const findIndex = <T = Recordable>(ary: Array<T>, fn: Fn): number => {
  12.   if (ary.findIndex) {
  13.     return ary.findIndex(fn)
  14.   }
  15.   let index = -1
  16.   ary.some((item: T, i: number, ary: Array<T>) => {
  17.     const ret: T = fn(item, i, ary)
  18.     if (ret) {
  19.       index = i
  20.       return ret
  21.     }
  22.   })
  23.   return index
  24. }
复制代码
关键点:

  • (参数: 类型): 返回类型 => {}
  • 泛型函数:
  • 默认参数:dom = document.documentElement
5.2 时间格式化函数
  1. // src/utils/index.ts
  2. export function formatTime(time: Date | number | string, fmt: string) {
  3.   if (!time) return ''
  4.   else {
  5.     const date = new Date(time)
  6.     const o = {
  7.       'M+': date.getMonth() + 1,
  8.       'd+': date.getDate(),
  9.       'H+': date.getHours(),
  10.       'm+': date.getMinutes(),
  11.       's+': date.getSeconds(),
  12.       'q+': Math.floor((date.getMonth() + 3) / 3),
  13.       S: date.getMilliseconds()
  14.     }
  15.     // ... 格式化逻辑
  16.     return fmt
  17.   }
  18. }
复制代码
关键点:

  • 联合类型参数:Date | number | string
  • 对象字面量类型自动推断
5.3 数字和金额处理
  1. // src/utils/index.ts
  2. // 数组求和
  3. export const getSumValue = (values: number[]): number => {
  4.   return values.reduce((prev, curr) => {
  5.     const value = Number(curr)
  6.     if (!Number.isNaN(value)) {
  7.       return prev + curr
  8.     } else {
  9.       return prev
  10.     }
  11.   }, 0)
  12. }
  13. // 元转分
  14. export const yuanToFen = (amount: string | number): number => {
  15.   return convertToInteger(amount)
  16. }
  17. // 分转元
  18. export const fenToYuan = (price: string | number): string => {
  19.   return formatToFraction(price)
  20. }
  21. // ERP 格式化数字
  22. export const erpNumberFormatter = (num: number | string | undefined, digit: number) => {
  23.   if (num == null) {
  24.     return ''
  25.   }
  26.   if (typeof num === 'string') {
  27.     num = parseFloat(num)
  28.   }
  29.   if (isNaN(num)) {
  30.     return ''
  31.   }
  32.   return num.toFixed(digit)
  33. }
复制代码
关键点:

  • 参数支持多种类型:string | number
  • 返回值类型明确:: number 或 : string
  • undefined 处理
六、Hooks(组合式函数)的 TypeScript

6.1 useTable Hook
  1. // src/hooks/web/useTable.ts
  2. interface ResponseType<T = any> {
  3.   list: T[]
  4.   total?: number
  5. }
  6. interface UseTableConfig<T = any> {
  7.   getListApi: (option: any) => Promise<T>
  8.   delListApi?: (option: any) => Promise<T>
  9.   exportListApi?: (option: any) => Promise<T>
  10.   response?: ResponseType
  11.   defaultParams?: Recordable
  12.   props?: TableProps
  13. }
  14. interface TableObject<T = any> {
  15.   pageSize: number
  16.   currentPage: number
  17.   total: number
  18.   tableList: T[]
  19.   params: any
  20.   loading: boolean
  21.   exportLoading: boolean
  22.   currentRow: Nullable<T>
  23. }
  24. /*
  25.   - reactive<TableObject<T>> 表示创建一个响应式对象,它的类型是 TableObject<T>
  26.   - 初始值是 { pageSize: 10, ... },必须符合 TableObject<T> 的结构
  27. */
  28. export const useTable = <T = any>(config?: UseTableConfig<T>) => {
  29.   const tableObject = reactive<TableObject<T>>({
  30.     pageSize: 10,
  31.     currentPage: 1,
  32.     total: 10,
  33.     tableList: [],
  34.     params: {
  35.       ...(config?.defaultParams || {})
  36.     },
  37.     loading: true,
  38.     exportLoading: false,
  39.     currentRow: null
  40.   })
  41.   const paramsObj = computed(() => {
  42.     return {
  43.       ...tableObject.params,
  44.       pageSize: tableObject.pageSize,
  45.       pageNo: tableObject.currentPage
  46.     }
  47.   })
  48.   const methods = {
  49.     getList: async () => {
  50.       tableObject.loading = true
  51.       const res = await config?.getListApi(unref(paramsObj)).finally(() => {
  52.         tableObject.loading = false
  53.       })
  54.       if (res) {
  55.         // 不管 res 原本是什么类型,我强制把它当成 ResponseType 来用
  56.         tableObject.tableList = (res as unknown as ResponseType).list
  57.         tableObject.total = (res as unknown as ResponseType).total ?? 0
  58.       }
  59.     }
  60.     // ... 其他方法
  61.   }
  62.   return {
  63.     tableObject,
  64.     methods
  65.   }
  66. }
复制代码
关键点:

  • 泛型 Hook:
  • 接口定义配置和状态
  • reactive 定义响应式对象
  • 双重类型断言:as unknown as ResponseType,

    • 先断言成 unknown
    • 再断言成 ResponseType
    • 用来强制转换原本不兼容的类型,绕过 TS 检查

6.2 组件引用类型
  1. // src/hooks/web/useTable.ts
  2. import { ElTable } from 'element-plus'
  3. /*
  4.   它是Table 组件实例 + TableExpose 接口的交叉类型
  5.   既是 Table 组件的实例,又包含 TableExpose 里定义的方法。等价于:
  6.   type TableRef = InstanceType<typeof Table> & TableExpose
  7. */
  8. const tableRef = ref<typeof Table & TableExpose>()
  9. const elTableRef = ref<ComponentRef<typeof ElTable>>()
  10. const register = (ref: typeof Table & TableExpose, elRef: ComponentRef<typeof ElTable>) => {
  11.   tableRef.value = ref
  12.   elTableRef.value = elRef
  13. }
复制代码
关键点:

  • typeof 获取类型
  • & 交叉类型(同时满足多个类型)
  • ComponentRef 组件实例类型
typeof 用法:
  1. function createUser(name: string, age: number) {
  2.   return {
  3.     name,
  4.     age,
  5.     sayHello() {
  6.       console.log(`Hi, I'm ${name}`)
  7.     }
  8.   }
  9. }
  10. type CreateUserFn = typeof createUser
  11. const myCreateUser: CreateUserFn = (n, a) => {
  12.   return { age: a, sayHello() {} }  // ❌ 报错。缺少name
  13. }
复制代码
七、常见类型使用技巧

7.1 类型断言
  1. // 断言为特定类型
  2. const value = someValue as string
  3. // 先断言为 unknown,再断言为目标类型
  4. const data = res as unknown as ResponseType
  5. // 表单数据的类型断言
  6. /*
  7.   - formData.value.id:某个表单数据的 id 字段
  8.   - undefined:赋的值是 undefined
  9.   - as number | undefined:告诉 TS,这个 id 的类型是 number | undefined
  10.   如果直接写:formData.value.id = undefined,TS 可能推断 id 的类型是 undefined,后面你想给它赋数字时会报错
  11.   其实不用断言:id: number | undefined
  12. */
  13. formData.value.id = undefined as number | undefined
复制代码
7.2 可选链和空值合并
  1. // 可选链:?.
  2. treeRef.value?.filter(val)
  3. config?.getListApi(params)
  4. // 空值合并:??
  5. const total = response.total ?? 0
  6. const lang = wsCache.get(CACHE_KEY.LANG) || 'zh-CN'
复制代码
7.3 数组和对象的类型
  1. // 数组类型
  2. const list: string[] = []
  3. const roles: number[] = [1, 2, 3]
  4. const users: UserVO[] = []
  5. // 对象类型
  6. const obj: { [key: string]: any } = {}
  7. const params: Recordable = {}
  8. // Map 和 Set
  9. const map = new Map<string, UserVO>()
  10. const set = new Set<string>()
复制代码
7.4 函数类型
  1. // 函数类型定义
  2. /*
  3.   Callback 是一个函数类型,这个函数接收一个 data 参数(任意类型),不返回任何值
  4.     - (data: any) => void:这是一个函数类型
  5.     - 示例:
  6.       type Callback = (data: any) => void
  7.       // 1. 定义一个符合 Callback 类型的函数
  8.       const handleSuccess: Callback = (data) => {
  9.         console.log('成功:', data)
  10.       }
  11. */
  12. type Callback = (data: any) => void
  13. type ApiFunction = (params: any) => Promise
  14. // 箭头函数
  15. const handleClick = (id: number): void => {
  16.   console.log(id)
  17. }
  18. // async 函数
  19. const fetchData = async (id: number): Promise<UserVO> => {
  20.   const res = await api.getUser(id)
  21.   return res
  22. }
复制代码
八、项目中的高级 TypeScript 用法

8.1 泛型约束
  1. // 约束泛型必须包含某些属性
  2. /*
  3.   K extends keyof T:K 必须是 T 的键之一。
  4.   - 不安全的写法:
  5.     function getPropertyUnsafe(obj: any, key: string) {
  6.       return obj[key]  // 返回值类型是 any,不安全
  7.     }
  8.     const value = getPropertyUnsafe(user, 'xxx')  // ✅ 不报错,但运行时可能 undefined
  9. */
  10. function getProperty<T, K extends keyof T>(obj: T, key: K) {
  11.   return obj[key]
  12. }
  13. // 使用示例
  14. const user = { name: '张三', age: 20 }
  15. const name = getProperty(user, 'name') // OK
  16. const gender = getProperty(user, 'gender') // 错误:gender 不在 user 中
复制代码
8.2 工具类型
  1. // Partial:所有属性变为可选
  2. /*
  3.   // 原始类型
  4.   interface UserVO {
  5.     id: number
  6.     name: string
  7.   }
  8.   // 使用 Partial
  9.   type PartialUser = Partial<UserVO>
  10.   // 等价于手动写:
  11.   type PartialUser = {
  12.     id?: number
  13.     name?: string
  14.   }
  15. */
  16. type PartialUser = Partial<UserVO>
  17. // Required:所有属性变为必填
  18. type RequiredUser = Required<UserVO>
  19. // Pick:选择部分属性
  20. type UserBasic = Pick<UserVO, 'id' | 'username' | 'nickname'>
  21. // Omit:排除部分属性
  22. type UserWithoutPassword = Omit<UserVO, 'password'>
  23. // Record:创建对象类型
  24. /*
  25.   使用 TypeScript 内置工具类型 Record<K, V> 创建一个"键值对映射"类型
  26.     - Record<K, V>:创建一个对象类型
  27.     - Record<number, UserVO>
  28.    
  29.   等价于手动写:
  30.     type UserMap = {
  31.       [key: number]: UserVO
  32.     }
  33.   示例:
  34.     interface UserVO {
  35.       id: number
  36.       name: string
  37.       email: string
  38.     }
  39.     type UserMap = Record<number, UserVO>
  40.     // 使用
  41.     const users: UserMap = {
  42.       1: { id: 1, name: '张三', email: 'zhang@example.com' },
  43.       2: { id: 2, name: '李四', email: 'li@example.com' },
  44.       100: { id: 100, name: '王五', email: 'wang@example.com' }
  45.     }
  46.     // 访问
  47.     const user1 = users[1]      // UserVO 类型
  48.     const user2 = users[2]      // UserVO 类型
  49.     // ❌ 键必须是 number
  50.     const invalid: UserMap = {
  51.       'abc': { id: 1, name: '张三', email: 'zhang@example.com' }  // 报错
  52.     }
  53. */
  54. type UserMap = Record<number, UserVO>
复制代码
8.3 条件类型
  1. // 根据条件选择类型
  2. type IsString<T> = T extends string ? true : false
  3. // 使用示例
  4. type A = IsString<string> // true
  5. type B = IsString<number> // false
  6. // 项目中的使用
  7. type Recordable<T = any, K = string> = Record<K extends null | undefined ? string : K, T>
复制代码
8.4 模板字面量类型
  1. // 动态生成类型
  2. type EventName = 'click' | 'change' | 'input'
  3. // Capitalize<T> 是 TS 内置工具类型:type A = Capitalize<'click'>   // 'Click'
  4. type EventHandler = `on${Capitalize<EventName>}`
  5. // 结果:'onClick' | 'onChange' | 'onInput'
复制代码
九、实战案例

案例 1:用户管理页面
  1. // src/views/system/user/index.vue
复制代码
案例 2:表单组件
  1. // src/views/system/user/UserForm.vue
复制代码
案例 3:Pinia Store
  1. // src/store/modules/dict.ts
  2. import { defineStore } from 'pinia'
  3. import { store } from '../index'
  4. import { DictDataVO } from '@/api/system/dict/types'
  5. import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
  6. import { getSimpleDictDataList } from '@/api/system/dict/dict.data'
  7. const { wsCache } = useCache('sessionStorage')
  8. export interface DictValueType {
  9.   value: any
  10.   label: string
  11.   clorType?: string
  12.   cssClass?: string
  13. }
  14. export interface DictTypeType {
  15.   dictType: string
  16.   dictValue: DictValueType[]
  17. }
  18. export interface DictState {
  19.   dictMap: Map<string, any>
  20.   isSetDict: boolean
  21. }
  22. export const useDictStore = defineStore('dict', {
  23.   state: (): DictState => ({
  24.     dictMap: new Map<string, any>(),
  25.     isSetDict: false
  26.   }),
  27.   getters: {
  28.     getDictMap(): Recordable {
  29.       const dictMap = wsCache.get(CACHE_KEY.DICT_CACHE)
  30.       if (dictMap) {
  31.         this.dictMap = dictMap
  32.       }
  33.       return this.dictMap
  34.     },
  35.     getIsSetDict(): boolean {
  36.       return this.isSetDict
  37.     }
  38.   },
  39.   actions: {
  40.     async setDictMap() {
  41.       const dictMap = wsCache.get(CACHE_KEY.DICT_CACHE)
  42.       if (dictMap) {
  43.         this.dictMap = dictMap
  44.         this.isSetDict = true
  45.       } else {
  46.         const res = await getSimpleDictDataList()
  47.         const dictDataMap = new Map<string, any>()
  48.         res.forEach((dictData: DictDataVO) => {
  49.           const enumValueObj = dictDataMap[dictData.dictType]
  50.           if (!enumValueObj) {
  51.             dictDataMap[dictData.dictType] = []
  52.           }
  53.           dictDataMap[dictData.dictType].push({
  54.             value: dictData.value,
  55.             label: dictData.label,
  56.             colorType: dictData.colorType,
  57.             cssClass: dictData.cssClass
  58.           })
  59.         })
  60.         this.dictMap = dictDataMap
  61.         this.isSetDict = true
  62.         wsCache.set(CACHE_KEY.DICT_CACHE, dictDataMap, { exp: 60 })
  63.       }
  64.     },
  65.     getDictByType(type: string) {
  66.       if (!this.isSetDict) {
  67.         this.setDictMap()
  68.       }
  69.       return this.dictMap[type]
  70.     },
  71.     async resetDict() {
  72.       wsCache.delete(CACHE_KEY.DICT_CACHE)
  73.       await this.setDictMap()
  74.     }
  75.   }
  76. })
  77. export const useDictStoreWithOut = () => {
  78.   return useDictStore(store)
  79. }
复制代码
十、常见错误和解决方案

错误 1:类型 "xxx" 上不存在属性 "yyy"
  1. // 错误示例
  2. const user = { name: '张三' }
  3. console.log(user.age) // 错误:类型"{name: string}"上不存在属性"age"
  4. // 解决方案 1:定义完整类型
  5. interface User {
  6.   name: string
  7.   age?: number // 可选属性
  8. }
  9. const user: User = { name: '张三' }
  10. console.log(user.age) // OK
  11. // 解决方案 2:类型断言
  12. console.log((user as any).age) // OK,但不推荐
复制代码
错误 2:类型 "undefined" 不能赋值给类型 "xxx"
  1. // 错误示例
  2. const formData = ref<UserVO>({}) // 错误
  3. // 解决方案:属性设为可选或提供默认值
  4. const formData = ref<Partial<UserVO>>({}) // 使用 Partial
  5. // 或者提供完整默认值
  6. const formData = ref<UserVO>({
  7.   id: undefined,
  8.   username: '',
  9.   nickname: ''
  10. })
复制代码
错误 3:参数 "xxx" 隐式具有 "any" 类型
  1. // 错误示例
  2. const handleClick = (item) => {
  3.   // 错误
  4.   console.log(item.id)
  5. }
  6. // 解决方案:显式标注类型
  7. const handleClick = (item: UserVO) => {
  8.   console.log(item.id)
  9. }
复制代码
错误 4:无法调用可能是 "undefined" 的对象
  1. // 错误示例
  2. formRef.value.validate() // 错误:可能是 undefined
  3. // 解决方案 1:可选链
  4. formRef.value?.validate()
  5. // 解决方案 2:判断后调用
  6. if (formRef.value) {
  7.   formRef.value.validate()
  8. }
  9. // 解决方案 3:非空断言(确定不为空时使用)
  10. // 我向 TypeScript 保证:在这里 formRef.value 一定不是 null 或 undefined,你放心当成非空来用。
  11. formRef.value!.validate()
复制代码
十一、最佳实践总结

1. 类型定义原则


  • ✅ 优先使用 interface 定义对象结构
  • ✅ 使用 type 定义联合类型、交叉类型
  • ✅ 简单类型可以让 TS 自动推断
  • ✅ 复杂类型明确标注
2. API 层


  • ✅ 为每个接口定义 VO/DTO 类型
  • ✅ API 函数参数和返回值标注类型
  • ✅ 使用 async/await 和 Promise
3. Store 层


  • ✅ 定义 State、Getters、Actions 的类型
  • ✅ 使用 interface 定义 State 结构
  • ✅ Getters 明确返回类型
  • ✅ Actions 参数标注类型
4. Views 层


  • ✅ 使用 ref() 或 reactive() 标注类型
  • ✅ defineProps() 定义 Props 类型
  • ✅ defineEmits() 定义 Emits 类型
  • ✅ 使用 FormRules 定义表单验证
5. 工具函数


  • ✅ 参数和返回值都要标注类型
  • ✅ 复杂函数使用泛型
  • ✅ 联合类型处理多种输入
6. 错误处理


  • ✅ 使用可选链 ?. 避免 undefined 错误
  • ✅ 使用空值合并 ?? 提供默认值
  • ✅ 适时使用类型断言 as
  • ⚠️ 避免过度使用 any
附录:常用类型速查表

类型说明示例string字符串const name: string = '张三'number数字const age: number = 20boolean布尔值const loading: boolean = truearray数组const list: string[] = []object对象const obj: { id: number } = { id: 1 }any任意类型const data: any = {}unknown未知类型const data: unknown = {}void无返回值const fn = (): void => {}null空const data: null = nullundefined未定义const data: undefined = undefinednever永不返回const fn = (): never => { throw new Error() }PromisePromiseconst fn = (): Promise => {}RefVue Refconst count = ref(0)ComputedRefVue Computedconst double = computed(() => count.value * 2)Nullable可空类型const id: Nullable = nullRecordable对象const obj: Recordable = {}PageParam分页参数const params: PageParam = { pageNo: 1, pageSize: 10 }结束语
本文档覆盖了项目中 绝大多数 的 TypeScript 使用场景。建议:

  • 先理解基础概念(interface、type、泛型)
  • 在实际编写代码时参考对应章节
  • 遇到错误时查看"常见错误和解决方案"
  • 多看项目代码,模仿学习
TypeScript 的核心是类型安全,合理使用类型可以:

  • ✅ 减少运行时错误
  • ✅ 提升代码可维护性
  • ✅ 提供更好的 IDE 智能提示
  • ✅ 让代码更加规范

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册