TypeScript从入门到实践:类型系统、泛型与工程迁移指南
如果你写过一段时间的JavaScript大概率经历过这种时刻一个函数跑得好好的换个调用方式突然就报错了一段别人留下的老代码改了一行数据格式十几个地方跟着崩又或者一个对象明明有某个字段你愣是想不起来它叫什么只能一遍遍console.log去翻。TypeScript解决的就是这类问题——它给JavaScript加了一层类型系统把很多运行时才会暴露的错误提前到写代码的瞬间就告诉你。这篇入门指南面向的是有JavaScript基础、想转型TypeScript的前端开发也会覆盖面试常考的核心概念我会尽量用实际开发里遇到的场景来讲而不是堆概念。主体内容不算短但我会按一条清晰的路线走先讲TypeScript到底治好了JavaScript的什么病然后从环境搭建开始逐步深入类型系统、泛型、高级类型最后给出存量项目的迁移路线和一个实战案例。放心读完之后你不仅能看懂别人的TS代码还能自己动手改造手里的项目。1. 为什么JavaScript越写越心慌TypeScript到底解决了什么1.1 动态类型是爽但坑也是真的多JavaScript最大的特点就是灵活变量可以随时从字符串变成数字对象可以随意增删属性函数参数不检查类型这些特性在小项目里写起来非常痛快。但项目一旦超过一定规模灵活性就会变成负担。我给你举一个特别典型的例子。假设你写了一个函数用来计算用户折扣后的订单金额function calcTotal(price, discount) { return price * discount; }函数本身很简单但调用方可能传入字符串const total calcTotal(99, 0.8); // 结果是 79.2不对是 NaN更麻烦的是这种问题不会在写代码的时候暴露而是要等到用户真的用到了这个页面、控制台刷出一行红色报错你才后知后觉。一个项目里这样的隐患如果攒了几百个那每次重构都是一场赌博——改一个公共函数你根本不知道哪些调用方会炸。1.2 TypeScript不是新语言而是带类型检查的JavaScript超集很多人一听新语言就被吓退了其实TypeScript的本质非常简单它就是JavaScript只是在上面叠加了一层类型标注。你写的TS代码最终会编译成JavaScript在浏览器或Node里运行所以TS能做的事JS都能做JS能做的事TS也都能做。区别在于TS在编译阶段会做一次静态检查。所谓静态检查就是不需要真正运行代码光靠读代码就能发现类型不匹配的问题。比如上面的calcTotal例子如果参数标注了number类型function calcTotal(price: number, discount: number): number { return price * discount; } calcTotal(99, 0.8); // 编辑器直接划红线报错在保存文件的那一刻编辑器就会告诉你这里传错了类型根本轮不到上线让用户踩坑。这就是类型安全的核心价值——把运行时的错误提前到编译期。1.3 类型安全的价值把运行时报错提前到编译期我自己的体会是类型系统真正的价值不只是少写几个bug而是它改变了你写代码的方式。当你给一个模块写清楚类型接口之后调用方只需要看类型签名就能知道该传什么、能拿到什么整个项目的沟通成本会直线下降。而且现在主流编辑器VS Code、WebStorm对TypeScript的提示支持都做得非常好。有了类型标注自动补全的效果完全不是一个量级对象有哪些属性、函数有哪些重载、参数该传字符串还是数字写代码的时候一目了然。注意类型检查只是帮助而不是保证。TS不会帮你拦截所有的逻辑错误比如price * discount算错了公式类型系统是发现不了的。它解决的是类型层面的错误不是业务逻辑错误这个定位要想清楚。2. 从零搭好TypeScript开发环境工具链与tsconfig精讲2.1 安装和第一个Hello World搭环境其实比很多人想象中简单。假设你已经有Node.js环境全局安装或者项目内安装TypeScript都可以npm init -y npm install -D typescript然后创建一个hello.ts文件写上一段带类型的代码function greet(name: string): string { return Hello, ${name}!; } const message: string greet(TypeScript); console.log(message);接着执行编译npx tsc hello.ts你会看到目录下多了一个hello.js文件这就是编译产物。如果你故意给greet传一个数字const message: string greet(123);再跑npx tsc hello.ts编译器会直接报错但仍然会生成JS文件——这个行为很多人一开始不习惯它的设计初衷是不阻塞编译但你要知道这里有错。如果你希望有错误时就停止生成可以在tsconfig里把noEmitOnError打开。2.2 tsconfig.json里的关键开关一个正经项目不会只靠命令行参数管理编译选项而是通过tsconfig.json统一配置。用命令初始化npx tsc --init你会得到一个带大量注释的配置文件。我挑几个影响最大的开关说说配置项作用我的建议strict开启所有严格类型检查包括空值检查、隐式any检查等新项目必须开迁移老项目可以渐进开启target指定编译输出的JavaScript版本按你的运行环境定现代环境用ES2020module指定模块系统常见的有CommonJS、ESNext看你的项目最终用什么打包器outDir编译产物输出目录建议设成dist别和源码混在一起rootDir源码根目录用来控制编译后的目录结构noUnusedLocals检查未使用的局部变量建议开启逼自己清理死代码noImplicitAny禁止隐式any强烈建议开启这是TS类型检查的底线strict这个开关尤其值得展开说。它不是一个独立选项而是一组严格检查的总开关内部包括strictNullChecks、noImplicitThis、alwaysStrict等多个子选项。对新手来说最直观的体验变化是strictNullChecks开启后null和undefined就不能随便塞给一个普通类型变量了这逼着你去处理值为空的情况。比如// strict: false 时下面这行不报错 let name: string null; // strict: true 时直接报错 let name: string null;想表达这个值可能是字符串也可能是空你要写let name: string | null null;这种写法看似麻烦但它逼你面对空值这种在真实项目里最容易出bug的场景。我见过太多线上事故都是某个字段是undefined代码没判断就直接用了。开着strict写代码这类事故会少很多。2.3 三种常见运行方式怎么选TS本身只是编译器编译后的JS怎么运行你可以根据自己的场景选择开发工具链完整用Vite、Webpack、Rollup这类打包器它们内部会处理TS编译你只需要把tsconfig.json配好。这也是目前前端项目最主流的方式。Node.js环境快速调试用tsx或者ts-node。tsx体验更好可以直接运行TS文件npx tsx hello.ts纯学习练手直接用官方提供的TypeScript Playground编辑器里的在线演练场不需要安装任何东西左边写TS右边看JS还能直接看类型推导结果。我个人建议新手把Playground用起来尤其是学类型体操的时候它能实时显示某个表达式推导出来的类型比在本地折腾环境高效得多。3. 核心类型系统从基础类型到泛型一次讲透3.1 基础类型和类型推断TypeScript的基础类型比JavaScript多了一套配套标注。数字、字符串、布尔、数组、对象这些你本来就在用只是多了个冒号写法let count: number 10; let title: string hello; let isDone: boolean false; let list: number[] [1, 2, 3]; let tuple: [string, number] [age, 30]; // 元组长度和位置固定不过更推荐的做法是尽量依赖类型推断。也就是说你能不写类型就不写类型让TS自己根据赋值推出来let count 10; // 自动推导为 number类型推断不是偷懒而是让代码更简洁。真正需要你显式标注类型的地方是函数参数和对外暴露的接口——这些地方的契约必须写清楚。还有几个容易被忽视的类型unknown不知道是什么类型。和any不同unknown不能直接调用方法或赋值给其他类型必须先做类型收窄。never永远不会有返回值的函数或者永远不会执行完的分支。比如抛异常的函数返回值就是never。void函数没有返回值时用它。any和unknown的区别面试经常问。我的理解是any是关闭类型检查等于把TS重新变回JS能少用就少用unknown是我不知道类型但我不放弃类型检查用之前必须判断。实际开发里解析外部接口返回的JSON数据时经常先用unknown接住再做类型断言或守卫。3.2 interface与type选谁更合适在TS里描述一个对象的形状有两种方式接口interface和类型别名type alias。interface User { id: number; name: string; } type UserType { id: number; name: string; };两者在大多数场景下可以互换但有一些差异值得记住interface支持声明合并同一个名字的接口可以定义多次TS会合并它们的属性。这个特性在扩展第三方库的类型时很有用。type更灵活可以表示联合类型、交叉类型、元组、原始类型别名等interface做不到的东西。interface可以extendstype可以用交叉类型实现类似效果。我的个人习惯是能用interface描述的对象优先用interface遇到联合类型、工具类型组合这种计算出来的类型再用type。这里还要提一个高频面试点interface和type的“extends”实现有什么不同。interface继承用的是extends关键字type组合用交叉类型。更关键的区别在于如果两个类型里有同名字段但类型不一致interface会报错交叉类型会得到never之类的诡异结果所以用组合时一定要小心字段冲突。3.3 泛型给函数和组件加上可插拔的类型泛型是TypeScript里最容易被新人卡住的概念但其实可以非常通俗地理解当你写一个函数时这个函数并不关心具体处理什么类型但又希望调用它时能保持类型信息就用泛型。举个例子你想写一个取数组第一个元素的函数。如果用any类型信息就丢了用泛型类型就能跟着调用场景走function firstT(arr: T[]): T | undefined { return arr[0]; } const num first([1, 2, 3]); // num 推导为 number const str first([a, b]); // str 推导为 string这个T就是一个类型变量你调用的时候TS会根据传入的数组自动推断出T具体是什么。读法上可以理解为函数first接收一个T类型的数组返回T类型或undefined。泛型还可以加约束用extends限定T必须满足某个形状function getLengthT extends { length: number }(arg: T): number { return arg.length; } getLength(hello); // 可以字符串有length getLength([1, 2, 3]); // 可以数组有length getLength(123); // 报错数字没有length这种约束在实际开发里特别常用尤其是封装通用组件、通用请求函数时。例如封装一个请求方法interface ApiResponseT { code: number; data: T; message: string; } async function fetchDataT(url: string): PromiseApiResponseT { const res await fetch(url); return res.json(); }调用的时候你可以指定想要的返回类型interface Product { id: number; name: string; } const data await fetchDataProduct(/api/product/1); // data.data 的类型就是 Product这一下就让接口返回的数据有了完整的类型提示不用再写一堆as any去断言了。4. 函数与类的类型化把JavaScript的自由收进笼子里4.1 函数参数、返回值与重载函数是JavaScript的核心类型化之后变化也最明显。基本写法function add(a: number, b: number): number { return a b; }或者用箭头函数表达式const add: (a: number, b: number) number (a, b) a b;默认参数、可选参数、剩余参数也都有对应的写法function buildName(first: string, last?: string): string { return last ? ${first} ${last} : first; } function sum(...nums: number[]): number { return nums.reduce((acc, n) acc n, 0); }函数重载是一个容易让新手困惑但也很有用的特性。它不是说同一个函数可以写多个实现而是同一个函数根据参数类型不同返回值类型也不同但内部只有一个实现。写重载时要先把所有签名列出来再写实现function parse(input: string): string[]; function parse(input: string[]): string; function parse(input: string | string[]): string | string[] { if (Array.isArray(input)) { return input.join(,); } return input.split(,); }这么写的好处是调用方传入数组时TS能精确知道返回的是string而不是string | string[]。这种根据输入推断输出的能力会让封装的工具函数好用很多。4.2 类的修饰符与抽象类TS里的类在JavaScript的class基础之上多了一套访问修饰符public默认任何地方都能访问。private只能在类内部访问。protected只能在类内部和子类中访问。readonly只读属性必须在声明时或构造函数里赋值。class Animal { protected name: string; private age: number; constructor(name: string, age: number) { this.name name; this.age age; } public speak(): void { console.log(${this.name} makes a sound); } }private在TS里是编译期检查也就是说编译成JS之后它其实还是普通的属性运行时无法真正阻止访问。这也是面试里经常提到的点TS的访问修饰符更多是给开发者看的约定不是运行时安全机制。抽象类也是TS相对JS新增的概念。用abstract修饰的类不能直接实例化只能被继承抽象方法必须由子类实现abstract class Shape { abstract getArea(): number; toString(): string { return Area: ${this.getArea()}; } } class Circle extends Shape { constructor(private radius: number) { super(); } getArea(): number { return Math.PI * this.radius ** 2; } }这种定义骨架、让子类填空的模式在需要强制子类实现统一接口时特别好用。不过实际项目里我更推荐优先用interface 组合的方式抽象类容易造成过深的继承链维护起来反而麻烦。4.3 类型收窄与类型守卫当你拿到一个联合类型比如string | number时怎么确定当前这个值到底是哪个类型这个过程叫类型收窄narrowing实现收窄的代码叫类型守卫。最简单的是typeoffunction format(input: string | number): string { if (typeof input string) { return input.trim(); } return input.toFixed(2); }typeof只适合判断基础类型。对象类型需要换别的手段instanceof判断是否某个类的实例。in判断对象是否存在某个属性。自定义类型谓词返回值写成arg is SomeType。自定义类型谓词是个很强大的工具比如你有一个用户和游客的联合类型interface User { id: number; email: string; } interface Guest { token: string; } function isUser(item: User | Guest): item is User { return email in item; } function handle(item: User | Guest) { if (isUser(item)) { console.log(item.email); // 这里item被收窄为User } else { console.log(item.token); // 这里item被收窄为Guest } }掌握类型守卫的意义在于很多运行时判断逻辑判空、判字段存在、判断数组/对象都可以顺便把类型收窄做掉一箭双雕。5. 高级类型实战映射类型、条件类型和内置工具类型5.1 联合类型与交叉类型的应用场景联合类型用|表示或交叉类型用表示且。看起来简单但应用场景非常多。联合类型最常见的场景是描述这个值只能是这几个之一type ButtonSize small | medium | large; type Status success | error | loading;字符串字面量联合类型在业务里特别好用它相当于把魔法字符串变成了一组可见的合法值写错了编译器直接报错比文档管用多了。交叉类型则常用于组合多个对象类型type BasicInfo { name: string; age: number }; type Contact { email: string; phone: string }; type Person BasicInfo Contact;交叉类型在复用一个公共基础类型又需要追加字段时很顺手。但注意别和interface继承混用出歧义如果两个类型定义了同名字段且类型不一致的结果会让你调试到崩溃。5.2 映射类型和条件类型的原理映射类型mapped type是遍历已有类型的每个属性生成一个新类型。最经典的例子是TS内置的Partial它的原理大致是type PartialT { [P in keyof T]?: T[P]; };这里的keyof T取出T的所有属性名组成一个联合类型in遍历这个联合类型T[P]取对应属性的类型。理解了这段代码你就理解了Partial是怎么把每个属性变成可选的。再配上-修饰符还能把可选变成必选这就是Required的原理type RequiredT { [P in keyof T]-?: T[P]; };条件类型conditional type则是根据某个类型判断的结果选择不同的类型type IsStringT T extends string ? true : false;T extends string ? true : false这行代码的意思是如果T能被赋值给string类型结果就是true否则是false。条件类型配合泛型可以用来写一些类型层面的逻辑。不过这类技巧在封装库时才常用日常业务开发里更常见的是直接用内置工具类型。5.3 几个高频内置工具类型解析如果你没时间深究类型体操至少要把这几个内置工具类型用熟它们能省下大量重复的类型定义工具类型作用使用场景PartialT所有属性变为可选表单更新的入参RequiredT所有属性变为必选必须要填全字段的接口PickT, K从T中挑出一部分属性列表页只需要部分字段OmitT, K从T中排除一部分属性创建数据时排除idRecordK, T构造一个键为K类型、值为T类型的对象枚举映射、字典结构ReturnTypeT获取函数类型的返回值类型推导函数返回类型ParametersT获取函数参数的类型元组包装函数时透传参数举个例子Record在写字典映射时非常好用type HttpCode number; type HttpMessage RecordHttpCode, string; const messages: HttpMessage { 200: OK, 404: Not Found, 500: Internal Server Error, };再看Pick和Omit的对比。假设你有一个完整的Product类型但前端列表页只需要id和name就完全没必要重新定义一个瘦身接口interface Product { id: number; name: string; price: number; description: string; createdAt: Date; } type ProductListItem PickProduct, id | name; type ProductCreateInput OmitProduct, id | createdAt;这样ProductCreateInput会跟着Product的变化自动更新加了一个字段创建入参也自动带上少维护一组容易漂移的类型定义。6. 存量JavaScript项目平滑迁移的实操路线6.1 渐进式改造allowJs与checkJs的妙用很多人接手老项目时想引入TS但项目动辄几十个文件不可能一次性全部改完。渐进式迁移是唯一现实的做法。第一步先把tsconfig里打开两个开关{ compilerOptions: { allowJs: true, checkJs: false, outDir: dist }, include: [src] }allowJs: true让TS编译器可以处理.js文件这样你就能把TS和JS混在一个项目里。然后在新写的文件里用.ts后缀老的.js文件先不动等编译通过、构建管线稳定后再逐个把.js改成.ts。第二步把老文件从完全不检查逐步过渡到检查但不报错。你可以在某个.js文件顶部加一行注释// ts-check这会让TS对这个文件做类型检查但不会硬性阻止编译。你会发现编辑器开始给你划黄线提示但不影响运行。根据提示一个一个改改完一个挪一个。第三步把checkJs改成true或者直接把strict打开让整个项目进入严格模式。这时候剩下的小问题会暴露出来但因为你已经渐进改了大半压力会小很多。提示改tsconfig时建议先提交一次git然后再逐项打开严格检查。如果报错太多不要硬扛可以先把strict关掉等文件迁移得差不多了再开。记住strict是一个目标不是一个起点。6.2 第三方库的类型声明怎么处理迁移过程中最常见的问题是项目里用了一堆第三方库这些库本身没有写TS类型编辑器里全是红色的Could not find a declaration file报错。解决办法分几种情况库自带类型直接能用比如axios、lodash的官方包就带了.d.ts文件。社区类型包大部分流行库在types组织下都有类型声明安装方式npm install -D types/node npm install -D types/react没有类型也没有社区类型自己写一个最小声明文件。在项目里创建一个src/types/xxx.d.tsdeclare module some-untyped-lib { export function doSomething(input: string): void; const defaultExport: any; export default defaultExport; }自己写声明文件的粒度按需来用到什么声明什么不用为了完整把整个库摸一遍。对业务代码来说一个只声明了你用到的函数的declare module就够了。6.3 迁移中的经典翻车现场我迁移过几个老项目踩过的坑总结下来就这几类第一any泛滥。刚开始改不动很多人会图省事把所有报错的地方都改成any。结果就是TS的壳、JS的核类型检查形同虚设。我的建议是一段代码如果实在搞不清类型用unknown接住配合类型守卫收窄如果必须用any加一行注释说明为什么这里放弃了类型检查至少给下一任维护者留个交代。第二回调函数和this指向。JS里函数内部的this是由调用方式决定的TS对this的检查有自己的规则。老代码里如果大量用function关键字而不是箭头函数迁移时经常出现this隐式是any或者this类型不匹配的报错。解决办法是明确标注this参数或者干脆趁迁移之机把相关逻辑改成箭头函数。第三类型和运行时混淆。刚学TS的人容易以为类型检查能替代运行时校验实际上接口返回的数据在运行时是不可信的。你声明了Product类型不代表后端一定会返回符合这个形状的数据。TS只是给你一个默认信任的模型真正的数据校验还是需要运行时处理的特别是用户输入和第三方接口的数据。7. 实战小案例用TypeScript重构待办事项模块7.1 先写一个能跑的JavaScript版本空谈类型系统太抽象我拿一个最常见的待办事项模块做例子。先用JavaScript写个看似正常的版本const todos []; function addTodo(title) { todos.push({ title, done: false }); } function toggleTodo(index) { todos[index].done !todos[index].done; } function renderTodo(todo) { return ${todo.title} - ${todo.done ? 已完成 : 未完成}; }这段代码的问题等你写多了自然能感觉到addTodo不检查title是不是字符串传个对象、数字都能塞进去。toggleTodo不检查index是否是有效数字越界直接报错。维护todo的人也不知道这个对象里到底有哪些字段全靠猜。如果这个模块只有你自己用还行一旦三个人一起开发很快就会有人写出todo.isCompleted true这种似乎合理但完全没用的代码。7.2 加上类型之后发生了什么用TypeScript重写一遍interface Todo { id: number; title: string; done: boolean; createdAt: Date; } type TodoStatus active | completed; class TodoStore { private todos: Todo[] []; private nextId 1; addTodo(title: string): Todo { const todo: Todo { id: this.nextId, title, done: false, createdAt: new Date(), }; this.todos.push(todo); return todo; } toggleTodo(id: number): void { const todo this.todos.find((t) t.id id); if (!todo) { throw new Error(Todo ${id} not found); } todo.done !todo.done; } getTodos(status?: TodoStatus): Todo[] { if (status completed) { return this.todos.filter((t) t.done); } if (status active) { return this.todos.filter((t) !t.done); } return [...this.todos]; } }对比一下就能看出几个明显变化Todo接口把这个对象长什么样写死了任何人改代码都能看到完整字段。toggleTodo参数从index换成了id并且做了找不到就抛错的处理杜绝了越界问题。TodoStatus把过滤状态限定为两个合法值传deleted这种字符串直接编译不过。这些改动没有增加太多代码量但整个模块的可维护性和安全性完全不同了。尤其是多人协作时其他人调用addTodo编辑器会自动提示需要一个string类型的title连函数的文档都不用看了。7.3 这次重构给我的直观感受你可能觉得这个例子太简单但真实项目里大量bug就是在这种简单模块里积累出来的。类型系统不给你的代码增加魔法它只是把约定变成了机器强制检查的规则。重构完之后最大的变化不是bug立刻消失了而是改代码的时候胆子变大了。以前改Todo的数据结构我得全局搜索所有用到的地方生怕漏掉一个现在TS会在所有用错的地方划红线我只要跟着报错一个个改就行。这种编译器当保镖的感觉用过一次就回不去了。如果有一天你要调整Todo的字段比如加一个priority字段需要做的只是更新Todo接口、更新创建处、然后看TS在哪里报错补哪里。这个流程带来的安全感和效率提升是任何代码审查流程都给不了的。8. 面试高频题与工程避坑清单8.1 面试官最爱问的TypeScript问题因为TypeScript现在几乎是前端岗位的标配面试里出现的频率非常高。我整理了最常见的几类问题你可以对照自测问题关键答题点interface和type有什么区别声明合并、联合类型支持、继承方式、同名字段冲突处理any和unknown有什么区别any关闭检查unknown保留检查、使用前必须收窄never类型有什么用表示不可能发生的值常用于穷举检查、抛错函数返回值泛型是什么怎么加约束类型参数T、extends限定形状、典型例子是数组处理和请求封装typeof和keyof怎么用typeof取值的类型、keyof取属性名联合类型什么是类型守卫typeof、instanceof、in、自定义类型谓词实现收窄如何让一个类型的所有属性可选Partial原理是映射类型[P in keyof T]?: T[P]implements和extends的区别实现接口要满足形状继承类可以获得实现和属性readonly和const有什么区别const是变量不可重新赋值readonly是属性不可修改怎么给没有类型的第三方库写声明在.d.ts里declare module按需声明用到的函数回答这些问题的核心不是背定义而是举出实际场景。比如问never你可以说写一个checkNever函数做穷举检查当联合类型新增成员时编译期就能发现漏处理的分支一说出场景面试官就知道你是真用过而不是背了八股。8.2 日常开发中必须养成的类型习惯最后聊几个我从实际项目里总结出来的习惯都是踩坑踩出来的第一不要一上来就追求最严格的类型。类型系统的目标是把代码写清楚不是给你自己找麻烦。如果一个类型写得太绕同事看不懂那这个类型的价值就打了折扣。能简单就不炫技这是我在团队里反复强调的原则。第二学会阅读类型报错。新手看到一大段带|和的报错就头大其实TS的错误信息里90%的线索都在第一行错误发生在哪个文件哪一行、期望什么类型、实际拿到什么类型。先把这三样读明白剩下的大段推导可以先跳过。第三多用编辑器提供的快速修复。VS Code里遇到类型错误鼠标悬停往往会有快速修复选项比如自动补全as const、自动添加类型断言。这对学习尤其有用你可以先让编辑器修一遍再看它改了什么理解它为什么这么改。第四给队友留逃生通道。引入TS的项目团队里一定有人不熟悉。可以约定如果某个三方库实在没有类型允许在.d.ts里做一个最小声明但不允许在业务代码里随便写as any。把临时妥协限制在可控范围内而不是让它扩散。第五把tsconfig纳入code review范围。很多人配好tsconfig就不动了其实随着项目发展一些严格检查是可以逐步打开的。比如noUnusedLocals、noFallthroughCasesInSwitch这种小开关打开之后能提前发现不少低级问题。每过一两个迭代花五分钟看看tsconfig里还有没有能开的开关收益非常稳定。我自己做技术选型时对要不要引入TypeScript这件事的答案几乎从来都是要。不是因为它时髦而是因为它确实解决了JavaScript在大规模协作中最痛的一个点类型信息只能靠人传人容易断、容易错、容易过时。类型把这份信息固化在代码里让编辑器、编译器、后来的维护者都能随时读到。这种把隐性的约定变成显性的检查的思路不仅在TypeScript里成立在任何需要多人协作的工程里都成立。如果你正打算在一个老项目里引入TS我的建议很简单别追求一步到位先配好allowJs从最核心的数据模型开始加类型每改完一个模块就跑一遍测试。这个过程不会一两天就结束但只要你坚持下来就会发现那个改一行代码心里就发慌的阶段不知不觉就过去了。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →