从「救命稻草」到「甜蜜的负担」:我对 TypeScript 的爱恨情仇

前几年面试时,简历上没写 "熟悉 TypeScript" 都不好意思投前端岗。那时候圈里都在传:"不会 TS 的前端迟早被淘汰"。我抱着焦虑感连夜啃文档,从interface学到泛型,从类型断言写到工具类型,总算赶在风口上搭上了这班车。

可现在敲代码时,看着屏幕上密密麻麻的类型声明,我总会盯着光标发呆 ------ 这玩意儿怎么越用越累了?

那些被类型绑架的日常

上周改个用户列表页,后端说接口返回加了个lastLoginTime字段。按以前用 JS 的习惯,直接user.lastLoginTime就能渲染,顶多在控制台报个undefined警告。

但现在得先找到User接口定义,在里面加一行lastLoginTime?: string;然后去改请求函数的返回类型,确保这个字段被正确包含;最后还要检查所有用到User类型的组件,看看有没有地方因为新增可选字段导致类型报错。前后折腾半小时,实际渲染代码就加了一行。

后端同事路过时瞥了眼屏幕:"我就加个字段,你这改的比我还多?" 我只能苦笑 ------ 他永远不懂,为了让user.lastLoginTime在编辑器里有正确的类型提示,我得付出多少额外工作。

更离谱的是上次处理表单组件。产品要做个动态表单,不同类型的字段有不同的校验规则。我光是定义类型就写了 200 多行:

ts 复制代码
interface InputField {

    type: 'input';

    props: {

        placeholder?: string;

        maxLength?: number;

        // ... 10多个属性

    };

    validator?: (value: string) => boolean;
}

interface SelectField {

    type: 'select';

    props: {

    options: { label: string; value: string }[];

    multiple?: boolean;

    // ... 又是一堆属性

    };

    validator?: (value: string | string[]) => boolean;

}

// 还有DatePicker、Checkbox... 最后组合成联合类型

type FormField = InputField | SelectField | DateField | CheckboxField;

等类型终于定义完,发现业务逻辑代码才写了 150 行。那天加班到十点,回家路上满脑子都是类型"string"不能赋给类型"number | undefined"的报错提示。

被神话的 "提前发现错误"

鼓吹 TS 的文章总说:"静态类型能在编译时发现错误,减少线上 bug"。这话没错,但现实往往是:我们花 80% 的精力解决的,都是 "类型错误" 而非 "业务错误"。

有次上线前,团队花了两天时间 "消灭红波浪线"。其中一半的错误是因为后端接口返回的status字段,有时候是number(1、2、3),有时候是string("1"、"2")。我们没去深究接口为什么这么设计,而是在前端写了一堆:

ts 复制代码
const status = res.status as unknown as number;

// 或者更离谱的

const status = Number(res.status) as 1 | 2 | 3;

最后线上还是出了 bug------ 不是因为类型不匹配,而是产品改了需求,某个状态的处理逻辑忘了同步更新。那些被我们精心维护的类型定义,在真正的业务漏洞面前毫无作用。

更讽刺的是,为了让 TS"满意",我们常常写出违背直觉的代码。比如处理空值时,明明可以if (data) { ... }简单判断,却非要写成:

ts 复制代码
if (data !== null && data !== undefined && Object.keys(data).length > 0) {

    // TS终于不报错了

}

这种为了适配类型系统而写的冗余代码,反而降低了可读性,埋下了新的隐患。

前后端的 "类型鸿沟"

后端同事的口头禅是:"我接口文档都写了,你前端自己适配下就行"。他们永远理解不了,为什么一个字段从number改成string,前端要改半天。

有次和后端联调支付接口,对方突然说:" 用户 ID 我打算从id改成userId"。我当场眼前一黑 ------ 这个字段贯穿了从登录到支付的 12 个接口、7 个组件和 3 个工具函数。

那天下午我什么业务都没做,光是全局搜索id,然后把所有相关的类型定义、接口声明、参数传递全部改了一遍。改到最后,后端又轻飘飘地说:"哦对了,还有个地方漏了,订单 ID 也得改"。

后来我们引入了 Swagger 自动生成 TS 类型,本以为能解放双手,结果生成的代码比我自己写的还离谱:

TS 复制代码
export interface UserResponse {

    code: number;

    data: {

        [key: string]: any;

        id?: number;

        name?: string;

        // 一堆可选属性

    };

    message?: string | null | undefined;

}

这种看似严谨的类型定义,实际和any没区别。最后还是得手动整理,等于做了两遍工作。

什么时候该松松绑?

前几天重构一个老项目,我试着用 JS 写了个模块。没有类型声明的束缚,思路反而顺畅了很多 ------ 不需要先构思完整的类型结构,想到哪写到哪,遇到问题直接在控制台调试,效率反而高了不少。

当然我不是说 TS 不好。在大型项目、团队协作时,它的类型约束确实能减少沟通成本。但我们是不是有点走火入魔了?

看到有人为了给setTimeout加类型,写出这样的代码:

ts 复制代码
const delay = <T>(ms: number, value: T): Promise<T> => {

    return new Promise((resolve) => {

        setTimeout(() => resolve(value), ms);

    });

};

我突然意识到,我们可能把 TS 当成了银弹,却忘了它只是个工具。就像用扳手拧螺丝,合适的扭矩能拧紧零件,太用力反而会滑丝。

或许前端圈需要一点反思:当类型声明比业务逻辑还复杂时,当为了适配类型系统而扭曲代码时,当团队因为类型问题争论不休时 ------ 我们是不是该停下来,问问自己:我们写代码是为了实现功能,还是为了满足 TS 的校验规则?

毕竟,用户不会因为你代码里的类型定义完美无缺,就多付一分钱。

相关推荐
会是上一次1 分钟前
企业级WEB应用服务器TOMCAT
java·前端·tomcat
anyup3 分钟前
🚀 2025 最推荐的 uni-app 技术栈:unibest + uView Pro 高效开发全攻略
前端·vue.js·uni-app
小喷友3 分钟前
第 12 章:最佳实践与项目结构组织
前端·react.js·next.js
ze_juejin4 分钟前
Nuxt.js 混合渲染模式(部分静态化+部分动态渲染)
前端
用户52709648744907 分钟前
Vue3 + Element Plus 报错:Cannot read properties of null (reading 'ce')
前端
ze_juejin14 分钟前
Nuxt.js SSR (服务端渲染) 的底层原理
前端
阿邱吖19 分钟前
实习小记(类名添加问题)&&运算符返回结果
前端
aoi28 分钟前
Monaco json 代码块中插入不可编辑,整块删除,整块光标跳过变量块
前端
用户8338102512244 分钟前
我为什么做PmMock:让接口设计不再头疼
前端·后端
我是ed1 小时前
# vue3 实现web网页不同分辨率适配
前端