最近在把负责的项目从js转成ts,一些经验记录,因为这个项目是新搭的,只有七八个路由页,总共十来个组件,改起来难度比较小(当然主要是我第一次自己改,自己搞就容易一切从简,只能说尽力做到除了接口泛型不用any)
工厂函数
工厂函数(Factory Function) 是一种「不依赖 new 关键字,通过普通函数封装对象 / 数据 / 实例的创建逻辑,最终返回目标结果」的设计模式。
在项目中我遇到最多的工厂模式的场景就是组件封装的时候使用的defineProps
interface PropsAction{
cc:string,
dd:number
}
const props = withDefaults(defineProps<{
aaa:string,
bbb:PropsAction
}>(),{
aaa:'',
bbb:()=>({
cc:''
dd:0
})
})
工厂模式整体上就是通过函数封装一个使用对象,来自豆包的例子
const createReactiveList = <T = DataType>(
defaultData: T[] = [] // 可选默认值,支持泛型(灵活适配不同类型)
): Ref<T[]> => {
// 内部封装:初始化 + 类型约束
const list = ref<T[]>(defaultData);
// 可额外封装操作逻辑(如添加、清空,按需扩展)
const addItem = (item: T) => {
list.value.push(item);
};
const clearList = () => {
list.value = [];
};
// 返回响应式数据 + 操作方法(或仅返回数据)
return {
list,
addItem,
clearList,
};
};
unknown和any的区别
unknown是ts的推荐+强安全性类型,any是不推荐的+无校验类型
unknown自带类型收缩,在真实的使用场景需要做类型校验才能够使用
const queryValue: unknown = route.query.aaa;
queryValue.toUpperCase();//会报错,无法使用,提示必须做类型检验
queryValue = queryValue + 1;//会报错,无法使用,提示必须做类型检验
// 🌟 类型收窄后,TS 允许使用(和你之前的查询参数处理逻辑一致)
if (typeof queryValue === 'string') {
queryValue.toUpperCase(); // ✅ 确认是 string 后,可安全调用方法
}
if (typeof queryValue === 'number') {
queryValue + 1; // ✅ 确认是 number 后,可安全运算
}
const queryValue2: any = route.query.aaa;
queryValue2.toUpperCase();//会正常通过不会报错提示
queryValue2 = queryValue2 + 1;//会正常通过不会报错提示
any一般应用的场景都在:
- 老项目迁移过渡用,方便快速转成ts,再支持后续迭代
- 第三方项目,不太清楚实际的内部结构,比如这次我的项目接的finebi平台,不知道一部分帆软的实际接口内部的类型定义,只能靠猜测一部分,一部分<T=any>去处理类型
类型守卫
本质上是在多种类型type的情况下,对于部分类型属性不兼容,导致需要收缩判断,去执行部分逻辑。
说人话就是:已知A数据一定是AAA|BBB|CCC中的一种,但不知道具体是哪一种,于是通过特定的条件判断,去确定在场景一A一定是AAA类型,场景二一定是BBB类型的函数或者语句。
interface AAA{
aaa:number,
ttt:number,
}
interface BBB{
bbb:number,
ttt:number,
}
interface CCC{
ccc:number,
ttt:number,
}
type TYPE = AAA|BBB|CCC
const isString = (x: unknown): x is string => {
return typeof x === 'string';
};
const isAAA = (A:TYPE): A is AAA=>{
return !!A && 'aaa' in A
}
const isAllowedIconKey = (x: string): x is VIconsKey => {
return ['HomeOutline', 'OptionsOutline', 'UserOutline'].includes(x);
};
keyof typeof
keyof 是 TypeScript 特有的操作符,作用是提取一个类型的所有键名,返回这些键名组成的字符串 / 数字联合类型
typeof 是判断值的类型(返回 "string"/"number" 等),而 TypeScript 中的 typeof 是获取一个值的完整类型,需要绑定as const做一个类型约束
当你需要提取类的key作为type约束
export const vIcons = {
NewspaperOutline: NewspaperOutline,
FolderOutline: FolderOutline,
OptionsOutline: OptionsOutline,
} as const; // 一定需要加as const 做一个类型约束
// 🌟 提取 vIcons 的键名类型(核心语法)
type VIconsKey = keyof typeof vIcons;
//本质上上面等于下面
type VIconsKey = 'NewspaperOutline'| 'FolderOutline' | 'OptionsOutline'
never
常用于错误兜底的场景,比如非正常的枚举分支/try catch错误捕获后的分支处理,提高项目健壮性
enum MenuStatus {
Normal = 'normal',
Disabled = 'disabled',
Hidden = 'hidden'
}
// 🌟 核心:兜底函数,接收 never 类型(只能传入不可能的类型)
function assertNever(value: never): never {
throw new Error(`未处理的类型:${value}`);
}
// 处理菜单状态(确保覆盖所有枚举值)
function handleMenuStatus(status: MenuStatus): string {
switch (status) {
case MenuStatus.Normal:
return '正常显示';
case MenuStatus.Disabled:
return '禁用状态';
case MenuStatus.Hidden:
return '隐藏状态';
default:
return assertNever(status);
}
}
type,interface和元组
- type: 给任意类型起别名,复用复杂类型
//联合类型
type abc = string|number
//交叉类型
type abc2 = BaseMenu & Menu
//元组
type abc3 = [number, string, number]
- interface:常用于接口入参,支持extends扩展和implements实现
// 示例1:基础接口(菜单配置,项目中最常用)
interface Menu {
label: string;
path: string;
icon?: string;
children?: Menu[]; // 嵌套自身,描述子菜单
}
// 示例2:接口扩展(继承其他接口,比type交叉类型更语义化)
interface MenuWithAuth extends Menu {
permissions: string[]; // 新增权限字段
}
const menu: MenuWithAuth = {
label: '首页',
path: '/dashboard',
permissions: ['read', 'write'],
};
// 示例3:类实现接口(面向对象场景)
interface Printable {
print: () => void;
}
class MenuService implements Printable {
print() {
console.log('打印菜单配置');
}
}
Typescript的数据类型
- 基础数据类型
string,number,undefined,null,symbol,boolean,bigint
- 复合类型
array,tuple,object,enum
tuple是特殊的,固定长度固定类型的数组,比如 const arr:[number,string,number] = [1,'2',3]
enum是命名常量,常用可枚举的类型限制
- 特殊类型
any,unknown,never,void
any 不安全类型
unknown推荐的类型限制,不清楚具体类型,于是在实际的代码执行层再进行类型限制
never 永远不会执行的类型,一般用于错误捕获
void 函数无返回值
- 高级类型
联合类型,交叉类型,类型断言
联合类型: let str:number | number
交叉类型: type strname = Name & Age
类型断言: let val: unknown = "hello";const str = val as string;