TypeScript实战中那些你不得不知道的类型优化技巧

子源 Dev 移动 阅读 682
赞 21 收藏
二维码
手机扫码查看
反馈

我的写法,亲测靠谱

我用 TypeScript 写移动端项目快五年了,踩过不少坑,也摸索出一套自己觉得顺手的写法。很多人一上来就想着把类型系统玩到极致,结果代码越写越绕,最后连自己都看不懂。其实 TS 的核心价值不是炫技,而是减少低级错误、提升协作效率。下面这些是我日常开发中反复验证过的做法。

TypeScript实战中那些你不得不知道的类型优化技巧

首先,接口定义别偷懒。我见过太多人直接用 any 或者 {} 敷衍了事,尤其是对接后端 API 的时候。这种写法短期内省事,后期改字段或者排查问题时能让你哭出来。

我的习惯是:每个 API 响应都配一个明确的 interface。比如:

interface UserResponse {
  id: number;
  name: string;
  avatar?: string; // 可选字段明确标出
  isActive: boolean;
}

// 调用时
const res = await fetch('https://jztheme.com/api/user/123');
const user: UserResponse = await res.json();

这里注意:可选字段一定要用 ? 标明。别图省事全写成必填,后端某个字段偶尔为空,你的页面就白屏了。我之前在一个活动页就这么栽过,上线后部分用户头像不显示,查了半天发现是 avatar 字段有时是 null,但 TS 类型里没标可选,导致运行时报错中断。

这几种错误写法,别再踩坑了

下面这些反面教材,我在 code review 里高频看到,新手老手都容易犯。

  • 滥用 as any:这是最危险的操作。有人遇到类型报错第一反应就是加 as any,看起来问题解决了,其实是把雷埋得更深。我建议:宁可多花十分钟写对类型,也不要动 as any。实在不行,用 unknown + 类型守卫过渡。
  • 函数返回值不写类型:TS 能自动推导,但显式声明更安全。尤其是异步函数,很多人写成 async function fetchData() { ... },结果返回值可能是 Promise<User | null>,调用方不知道要判空。我强制要求团队所有函数必须显式标注返回类型。
  • ObjectArray 当类型:比如 const data: Object = {}。这等于没写类型!应该用 Record<string, unknown> 或具体结构。

举个真实例子:有个同事写了个工具函数处理表单数据,用了 data: Object,结果传入 null 时没报错(因为 JS 里 null 是 object),运行时直接崩了。改成 Record<string, string | number> 后,编译器立刻提示传参错误。

实际项目中的坑

在移动端项目里,TS 和动态行为结合时特别容易出问题。比如你用 React 写一个下拉刷新组件,状态可能有 idleloadingsuccesserror 几种,这时候别用字符串字面量硬编码。

我一开始也图快,直接写:

const [status, setStatus] = useState('idle'); // 错!

结果某次手误写成 setStatus('loadin')(少个 g),TS 居然不报错,因为字符串类型太宽泛了。后来改成枚举或联合类型:

type RefreshStatus = 'idle' | 'loading' | 'success' | 'error';
const [status, setStatus] = useState<RefreshStatus>('idle');

现在只要拼错,编辑器立马红波浪线,省心多了。

另一个坑是事件处理。移动端经常要处理 touch 事件,比如 onTouchStart。很多人这样写:

const handleTouchStart = (e) => {
  console.log(e.touches[0].clientX);
};

TS 默认不知道 e 是什么类型,会报错。正确做法是指定类型:

const handleTouchStart = (e: React.TouchEvent) => {
  console.log(e.touches[0].clientX); // 安全访问
};

如果是原生 JS 环境(比如用 Vue 或纯 JS),就得用 TouchEvent

element.addEventListener('touchstart', (e: TouchEvent) => {
  // ...
});

别小看这个细节,我之前在一个 H5 项目里漏了类型,结果在 iOS 上某些机型 touches 是空数组,直接报 Cannot read property 'clientX' of undefined,线上事故。

关于泛型:别过度设计

泛型很强大,但很多人一上来就想搞个“万能 Hook”或者“通用请求封装”,结果类型参数套三层,维护成本爆炸。我的原则是:只在真正需要复用且类型不确定时才用泛型

比如封装一个简单的数据请求 Hook:

// 别这么写(过度抽象)
function useApi<T>(url: string): { data: T | null; loading: boolean } {
  // ...
}

// 我的实际写法:针对具体场景
interface User {
  id: number;
  name: string;
}

function useUser(id: number) {
  const [user, setUser] = useState<User | null>(null);
  // 具体逻辑...
  return { user };
}

除非你真的要做一个跨项目的请求库,否则为每个业务模块写专用 Hook 更清晰。泛型不是银弹,用不好反而增加理解成本。

当然,有些地方泛型必不可少。比如工具函数:

function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
  return keys.reduce((acc, key) => {
    acc[key] = obj[key];
    return acc;
  }, {} as Pick<T, K>);
}

这种通用工具函数,泛型能让类型精准传递,值得投入。

最后的小建议

别追求 100% 类型覆盖。有些动态场景(比如从 URL 解析参数),硬要写死类型反而麻烦。我通常用 zodio-ts 做运行时校验,TS 负责静态检查,两者互补。

另外,开启 strict 模式。虽然初期会有很多报错,但长期看能避免大量隐患。我现在的项目 tsconfig.json 里基本全开:

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true
  }
}

折腾完前两周确实头疼,但之后代码健壮性明显提升,尤其多人协作时,类型就是最好的文档。

以上是我踩坑后的总结,希望对你有帮助。有更好的方案欢迎评论区交流,比如你们怎么处理复杂的表单类型?我还在找更优雅的方式。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论