Skip to content
第 19 章 前端 ⏱ 11 分钟阅读

第 19 章:常见坑速查 ​

学习目标 ​

  • 识别 TS 新手最常踩的 15 个坑
  • 理解每个坑的根因和解决方案
  • 建立 TS 调试思路
  • 形成 TS 编码规范

一、坑速查表 ​

#坑表现
1滥用 any类型保护全失
2忘了 strictNullChecksnull/undefined 到处传
3函数表达式返回类型错写推导成"值的类型"
4元组 push 突破长度运行时破坏类型约束
5对象字面量多余字段"extra property" 报错
6private 字段运行时可访问以为私有其实能读
7const enum + isolatedModulesVite 项目不能用
8类型断言绕过类型检查as any 全无敌
9接口和 type 混淆重复定义报错
10Object.keys() 返回 string[]推导不到具体 key
11异步函数返回 Promise<any>res.json() 没校验
12== 收窄失效用了 != 而不是 !==
13装饰器没开 experimentalDecorators编译报语法错
14路径别名只在 tsconfig 配了Vite/webpack 编译失败
15for...in 包含原型链类型可能多出字段

二、详解 1:滥用 any ​

typescript
function process(data: any): void {  // ❌
  return data.foo.bar;
}

function process(data: unknown): void {  // ✅
  if (typeof data === 'object' && data !== null && 'foo' in data) {
    // 收窄
  }
}

三、详解 2:strictNullChecks ​

typescript
function greet(name: string | null): string {
  return `Hello, ${name.toUpperCase()}`;
  // ❌ Object is possibly 'null'
}

function greet(name: string | null): string {
  return `Hello, ${name!.toUpperCase()}`;
  // ⚠️ ! 断言不安全,改用条件判断
}

function greet(name: string | null): string {
  return `Hello, ${name?.toUpperCase() ?? 'Guest'}`;
  // ✅
}

四、详解 3:函数表达式返回类型 ​

typescript
// ❌ 把返回类型写在函数表达式上
const add: number = (a: number, b: number) => a + b;
// 这其实是给变量加类型,但函数本身推导为 number(无法返回 number)

// ✅ 正确写法
const add: (a: number, b: number) => number = (a, b) => a + b;

五、详解 4:对象字面量多余字段 ​

typescript
interface User { name: string }

function greet(u: User): void { console.log(u.name); }

// ❌ 直接传字面量报错
greet({ name: 'Tom', age: 18 });

// ✅ 改成变量
const u = { name: 'Tom', age: 18 };
greet(u);

六、详解 5:Object.keys 推断 ​

typescript
interface User {
  id: number;
  name: string;
}

const user: User = { id: 1, name: 'Tom' };
const keys = Object.keys(user);   // string[],不是 ('id' | 'name')[]

// 解决:用 keyof 断言
const keys = Object.keys(user) as Array<keyof User>;
keys.forEach(k => user[k]);

⚠️ 坑 1:Object.keys 的返回类型 TS 没法做到精确,运行时 key 顺序也不保证,生产代码慎用。

七、详解 6:异步 JSON 解析 ​

typescript
async function getUser(): Promise<User> {
  const res = await fetch('/api/user');
  return res.json();   // 实际是 Promise<any>,只是赋给了 Promise<User>
}

// ✅ 加 zod 校验
import { z } from 'zod';

const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
});

async function getUser(): Promise<User> {
  const res = await fetch('/api/user');
  const data = UserSchema.parse(await res.json());
  return data;
}

八、详解 7:装饰器配置 ​

json
// tsconfig.json
{
  "compilerOptions": {
    "experimentalDecorators": true   // 必须开
  }
}

不开启编译会报 Decorators are not valid here(TS 5.0+ 已把 experimental 标记为稳定部分,但旧项目仍需配置)。

九、详解 8:路径别名(双端) ​

json
// tsconfig.json
{
  "compilerOptions": {
    "paths": { "@/*": ["src/*"] }
  }
}
typescript
// vite.config.ts
import { defineConfig } from 'vite';
import path from 'path';

export default defineConfig({
  resolve: {
    alias: { '@': path.resolve(__dirname, 'src') },
  },
});

⚠️ 坑 2:tsconfig 的 paths 只影响类型检查,打包器必须单独配。

十、详解 9:for...in ​

typescript
interface User {
  id: number;
  name: string;
}

const user = Object.create({ extra: 'foo' });   // 原型链上有 extra
Object.assign(user, { id: 1, name: 'Tom' });

for (const key in user) {
  console.log(key);   // extra, id, name(包含原型链)
}

十一、详解 10:== 收窄 ​

typescript
function process(x: string | null): void {
  if (x == null) {   // ✅ 等价于 x === null || x === undefined
    return;
  }
  x.toUpperCase();
}

⚠️ 坑 3:项目里 == 只在收窄 null/undefined 时使用,其他地方必须 === / !==,ESLint 配 eqeqeq: ['error', 'always', { null: 'ignore' }]。

十二、TS 调试技巧 ​

bash
# 1. 看具体报错位置的上下文
tsc --pretty

# 2. 查看类型推导
tsc --traceResolution

# 3. 查看某个表达式的类型
import('typescript').then(ts => {
  // 用 checker
});

# 4. VSCode:光标移到变量上,看 hover 类型

十三、本章小结 ​

调试思路工具
报错信息看红色波浪线 + VSCode Problems 面板
hover 类型把鼠标放到变量上看弹出类型
tsc --traceResolution看模块解析路径
类型展开tsc --listFiles 看参与编译的文件
排查 any搜索 : any 看泄漏点

动手练习 ​

  1. any 大扫除:找一个老项目,统计 any 使用次数,改 unknown 看哪里需要适配
  2. null 守卫:把所有可能的 null/undefined 显式标注,加 ?. 或条件判断
  3. 配置核对:打开自己的 tsconfig.json,逐项对照本教程的推荐配置

下一章:第 20 章:进阶资源 →

本站基于 VitePress 构建 · 由 StackHub 团队维护