从零开始学前端 | 第二十五章:在前端项目中使用 TypeScript
本章定位走到这一章说明你已经完成了 TypeScript 第三阶段的核心语法学习。前面你已经系统学过为什么前端项目会越来越需要 TypeScript。基础类型与类型注解。接口、类型别名与函数类型。泛型与常见实用场景。也就是说到现在为止你已经能理解并写出这些内容string、number、boolean联合类型interfacetype函数类型泛型函数泛型列表结构和接口返回值结构这一步其实已经非常重要了。因为你已经不只是“知道 TypeScript 是什么”而是已经具备了用类型去描述数据结构和函数规则这样的基础能力。但如果你现在马上进入真实前端项目很快就会遇到一个新的问题这些类型知识放进一个真的项目里到底是怎么工作的例如你可能会开始关心.ts和.tsx到底有什么区别tsconfig.json是做什么的编辑器里那些红线和提示到底该怎么看为什么有些错误一改就通有些错误一改就牵一大片如果我有一个 JavaScript 小项目最稳的 TypeScript 迁移顺序应该是什么到了 React 项目里TypeScript 最常出现在哪些地方这一章要解决的核心问题就是TypeScript 不是只存在于单个示例代码里而是会真正参与前端项目的开发流程、报错反馈、结构设计和后续维护。也就是说这一章更像是把前面学过的 TypeScript 基础接到真实前端项目里。本章学习目标学完这一章后你应该能做到理解 TypeScript 在前端项目中的基本工作流程。知道.ts、.tsx、tsconfig.json各自大概在做什么。理解编辑器提示、类型报错和构建检查在项目里扮演的角色。学会阅读最常见的几类 TypeScript 报错。知道一个 JavaScript 小项目迁移到 TypeScript 的更稳顺序。知道前端项目里最值得优先补类型的几个位置。对 React 项目里的常见 TypeScript 场景建立第一层预习认知。避开初学者在“把 TypeScript 放进项目里”时最常见的误区。一、这一章为什么特别重要前面几章你学到的很多内容都还属于单点知识例如这个类型怎么写那个接口怎么定义泛型函数怎么表达这些当然都很重要。但真正进入项目以后你会慢慢发现项目里的 TypeScript不只是“写几个类型”而是一整套开发体验的一部分。它会参与文件组织编辑器补全报错提醒类型检查重构时的影响暴露后续维护时的结构理解也就是说从这一章开始你要建立的认知不再只是某一行类型怎么写而是TypeScript 在一个真实前端项目里到底是怎么帮你工作的这也是为什么这一章虽然不讲很多新语法但它非常关键。二、先把一件事说清楚项目里的 TypeScript 不只是“代码能不能运行”很多纯小白第一次接触项目里的 TypeScript 时会默认把它理解成“TypeScript 代码最后能不能跑起来”这当然是其中一部分。但如果只这样理解会低估它的价值。更完整的理解应该是TypeScript 更重要的价值往往体现在你写代码、改代码、看代码的整个过程里。1. 它会在你写代码时给提示例如某个对象有哪些字段某个函数需要什么参数某个返回值是什么类型2. 它会在你写错时更早提醒例如字段拼错参数类型不对空值没处理3. 它会在你改结构时帮你暴露影响范围例如你把userName改成nickname那么很多依赖这个字段的地方就更容易被及时暴露出来。所以从项目视角看TypeScript 的价值不只是“让代码跑”更是“让开发过程更稳”。三、在前端项目里TypeScript 通常是怎么工作的这一节非常重要。因为很多初学者会把 TypeScript 想成一门“浏览器直接运行的语言”。但更适合当前阶段的理解是你写的是带类型信息的开发代码TypeScript 会在开发阶段帮你检查再由工具链处理成浏览器能运行的代码。1. 你可以先把整体流程想成这样你写.ts或.tsx文件编辑器根据类型信息给出补全和报错提示项目里的 TypeScript 配置告诉工具应该怎么检查构建工具把项目打包成浏览器能运行的前端代码2. 这里最值得你先记住的是什么不是底层编译细节而是TypeScript 在项目里最先服务的是开发阶段而不是只在最后一刻才起作用3. 这也是为什么它和“项目开发体验”关系很大你越往后写 React、Next.js 项目就越会明显感受到没写完时就能先看到提示改一处结构时能更快发现影响看别人代码时更容易知道输入输出规则四、.ts和.tsx到底有什么区别这是进入真实项目后最先会遇到的问题之一。当前阶段你可以先记住一个最实用的判断方式1..ts写普通 TypeScript 逻辑文件例如工具函数类型定义数据处理逻辑API 请求封装都很常放在.ts文件里。例如exportinterfaceUserInfo{userName:string;age:number;}exportfunctionformatUserName(user:UserInfo):string{return你好${user.userName};}2..tsx写包含 JSX 的 TypeScript 文件如果文件里要写这种内容function WelcomeCard() { return h1你好前端世界/h1; }那它通常就要用.tsx。3. 当前阶段最稳的记法你可以先用一句非常实用的话记住没有 JSX 用.ts有 JSX 用.tsx这已经足够支撑你进入后面的 React 阶段了。五、tsconfig.json是什么这是很多人第一次进 TypeScript 项目时最容易被文件名吓到的一个配置文件。但当前阶段你完全不需要一上来就把所有配置项背下来。你现在只需要先把它理解成告诉 TypeScript这个项目要按什么规则来检查和处理代码1. 一个非常简化的示意例子{compilerOptions:{target:ES2020,strict:true,jsx:react-jsx},include:[src]}2. 这段配置当前阶段怎么理解就够了你可以先只抓住四个点compilerOptions表示一些核心检查和处理规则。target可以先理解成“面向什么样的 JavaScript 运行环境”。strict可以先理解成“要不要更严格地做类型检查”。jsx在 React 项目里会很常见和 JSX 的处理有关。include可以先理解成“哪些目录里的文件要纳入检查”。3. 当前阶段最重要的不是背配置项而是先建立这个认知tsconfig.json不是拿来制造神秘感的它本质上是在定义项目里的 TypeScript 规则六、为什么很多前端项目里你不需要手动从零配 TypeScript这一点很值得现在就知道。很多纯小白一看到配置文件就会有点紧张觉得“是不是我要自己从零把 TypeScript 项目全部搭出来”其实很多现代前端工具已经帮你做了很多事情。例如一些脚手架会自动生成tsconfig.json一些 React 项目模板天生就支持 TypeScript一些构建工具会默认给出比较合理的基础配置1. 这意味着什么意味着你现在学习这一章时重点并不是把所有配置背下来而是至少知道项目里这些文件和规则是干什么的遇到时不陌生2. 为什么这对纯小白特别重要因为这会帮你把注意力放回最核心的地方数据结构函数输入输出组件 props接口返回值也就是那些真正会长期影响代码质量的部分。七、在项目里编辑器提示到底在帮你什么这一节非常关键。因为很多人真正开始喜欢 TypeScript不是因为“编译”两个字而是因为编辑器变得更会帮忙了1. 最直观的一类帮助自动补全例如你有这样的类型interfaceUserInfo{userName:string;age:number;cityName?:string;}当你写constuserInfo:UserInfo{userName:小白,age:18};userInfo.编辑器通常就更容易提示你userNameagecityName2. 第二类帮助参数提醒例如functioncreateWelcomeMessage(user:UserInfo):string{return你好${user.userName};}当你调用createWelcomeMessage(...)编辑器通常就更容易告诉你这里应该传一个UserInfo3. 第三类帮助重构更安心例如你把字段名从userName改成nickname编辑器和类型系统就更容易一起帮你发现哪些使用这个字段的地方要跟着调整所以很多时候项目里 TypeScript 最先带来的不是“更多语法”而是更像一个会提醒你的开发助手八、怎么看编辑器里的类型报错这一节很重要。因为很多纯小白第一次把 TypeScript 放进项目时最大的心理压力不是“不会写”而是一看到红线就觉得自己哪都不对其实完全不用这样想。更稳的理解是报错是在告诉你这里的类型预期和实际使用不一致1. 看报错时先抓哪四件事当前阶段最推荐你先只抓这四件事报错发生在哪个文件报错发生在哪一行它期待的是什么类型你实际给的是什么类型2. 这四件事为什么够用因为绝大多数基础报错本质上都可以先归结成这里“本来应该是什么”但“你实际给成了别的”3. 不要一上来想把整段英文提示都啃完很多时候你只要先抓住stringnumberundefinedmissing property这些关键词就已经能开始定位问题。九、最常见的第一类报错类型不匹配这一类报错是最常见的。例如letcurrentPrice:number99;// 这里故意写错观察编辑器提示currentPrice99;这里更常见的提示思路通常会接近不能把string分配给number1. 它真正想提醒你什么就是你明明说这个变量应该是数字但现在给了字符串2. 这种报错最常见的处理思路先不要急着“骗过类型系统”。先问自己这里本来就应该是数字吗如果应该是数字为什么现在拿到的是字符串是输入数据的问题还是你写错了变量类型3. 为什么这个习惯很重要因为 TypeScript 不是让你只会“消灭红线”。更重要的是借着红线回到真实数据问题本身十、第二类常见报错对象缺少字段例如interfaceUserInfo{userName:string;age:number;}constuserInfo:UserInfo{userName:小白};这里更常见的提示思路通常会接近当前对象缺少age这个必填字段1. 它真正想表达什么就是你定义的对象结构说“必须有这些字段”但你实际给少了2. 这种报错的处理思路先问自己这个字段本来是不是就必须存在如果必须存在那就补齐数据如果它其实允许没有那是不是应该改成可选属性?3. 这里特别值得注意的一点不要一有报错就下意识把所有字段都改成可选。要先想清楚业务上它到底是“真的可选”还是“你漏传了”十一、第三类常见报错对象可能为undefined或null这类报错在前端项目里非常常见。因为很多数据并不是一开始就有。例如接口数据还没返回当前选中项还没产生某个可选字段可能不存在例如interfaceUserInfo{userName:string;cityName?:string;}functionprintCity(user:UserInfo):string{returnuser.cityName.toUpperCase();}这里的问题在于cityName可能没有所以直接拿来当一定有值的字符串使用就不稳1. 这类报错真正提醒的是什么不是“TypeScript 太烦了”而是你现在在假设一个可能为空的值一定存在2. 当前阶段最稳的处理思路先补一层判断。例如functionprintCity(user:UserInfo):string{if(!user.cityName){return城市未填写;}returnuser.cityName.toUpperCase();}3. 为什么这类报错在真实项目里特别有价值因为它会逼你更早面对一个真实问题这个值到底是不是一定存在这恰恰是很多线上 Bug 的来源。十二、第四类常见报错字段名写错例如interfaceUserInfo{userName:string;age:number;}constuserInfo:UserInfo{userName:小白,age:18};console.log(userInfo.username);这里你很可能只是大小写或者命名方式写错了。1. 这类报错最想提醒你什么就是当前对象结构里没有这个字段2. 为什么这类错误在 JavaScript 里很容易悄悄溜过去因为它常常不会在你写代码时就被马上指出来。有时你最后只会在运行时得到一个undefined3. 这也说明了 TypeScript 在项目里的一个重要价值就是很多“原本会拖到运行时才发现的问题”可以被尽量往前提十三、不要把“修报错”理解成“压红线”这一点很重要我想单独强调一下。很多初学者把 TypeScript 放进项目后最容易进入一种危险状态目标变成“红线赶紧没了”这其实不稳。因为红线没了不一定说明问题真的解决了。1. 更稳的心态应该是什么应该是红线只是入口我真正要修的是数据结构、边界判断和函数规则2. 为什么这个心态特别重要因为你后面学 React、Next.js 时会遇到更多状态可能为空props 可能缺失接口数据还没回来这些都不是“压掉红线”能真正解决的。只有回到业务和数据本身类型修复才会真的有价值。十四、把一个 JavaScript 小项目迁移到 TypeScript最稳的顺序是什么这就是本章非常实战的一节。因为很多人学完前几章后很自然就会想我能不能把自己以前写的小项目改成 TypeScript 版本可以而且很值得。但如果顺序不对就容易越改越乱。1. 最不推荐的做法一上来就想着把所有文件同时改成最完整、最严格、最完美的类型版本这对纯小白来说很容易失控。2. 更稳的迁移顺序当前阶段我更推荐你按下面这个顺序来先识别项目里的核心数据结构先给核心对象和列表补类型再给关键函数补参数和返回值类型再处理接口返回值和状态变量最后再修零散报错和局部细节3. 为什么这个顺序更稳因为前端项目里最重要的类型信息通常都藏在数据模型函数边界页面状态这些核心位置。只要这些地方先清楚了后面很多局部问题都会变得更容易处理。十五、迁移时最值得优先补类型的四个位置这一节你完全可以当成一个实战清单来记。1. 核心数据结构例如用户信息商品信息待办项表单数据这些最值得优先定义成interfacetype因为它们会影响整个项目后面的很多逻辑。2. 关键函数的输入输出例如提交函数计算函数格式化函数数据转换函数这些函数的参数和返回值如果清楚项目会稳很多。3. 接口返回值结构这在前端项目里几乎是高频重点。因为接口数据一旦类型模糊后面页面渲染、状态更新、事件处理都会跟着模糊。4. 页面状态例如当前是否加载中当前用户信息当前筛选状态当前选中项这些状态一旦有了明确类型很多页面逻辑会明显更清楚。十六、一个小型待办项目迁移示例先从数据结构开始下面我们用一个非常小的例子来看看“从 JavaScript 迁移到 TypeScript”最稳的感觉是什么。1. 先看 JavaScript 版本consttodoList[{id:1,text:学习 TypeScript,completed:false}];functionaddTodo(text){todoList.push({id:Date.now(),text,completed:false});}这段代码当然能跑。但它有几个问题text是什么类型没有写清楚todoList里每项到底长什么样只是靠默认理解如果以后字段变了影响范围不清楚2. 再看 TypeScript 版本interfaceTodoItem{id:number;text:string;completed:boolean;}consttodoList:TodoItem[][{id:1,text:学习 TypeScript,completed:false}];functionaddTodo(text:string):void{todoList.push({id:Date.now(),text,completed:false});}3. 这个迁移版本最先补的是什么不是每个局部变量。而是TodoItem结构todoList列表类型addTodo的参数和返回值这就是一个非常稳的迁移顺序。十七、再往前一步接口返回值也值得尽早补类型前端项目一旦开始有请求逻辑接口返回值类型几乎马上就会变成重点。例如先看一个比较模糊的写法asyncfunctiongetTodoList(){constresponseawaitfetch(/api/todos);returnresponse.json();}这段代码当然也能写。但问题在于返回的数据结构不清楚如果我们先补一层类型就会清晰很多。interfaceTodoItem{id:number;text:string;completed:boolean;}interfaceApiResponseT{code:number;message:string;data:T;}asyncfunctiongetTodoList():PromiseApiResponseTodoItem[]{constresponseawaitfetch(/api/todos);returnresponse.json();}1. 这里的重点不是把写法变复杂而是让“这个接口到底会返回什么”这件事更明确2. 为什么这在项目里特别重要因为后面页面里一旦要写列表渲染统计数量切换完成状态都建立在你对返回数据结构的理解上。十八、在 React 项目里TypeScript 最常先出现在哪些地方这一节我们只做预习不展开太深。因为下一阶段你会系统学习 React。但现在非常值得先建立一层认知React 项目里的 TypeScript最常先出现在 props、state、事件和接口数据上1.props也就是组件接收的外部数据。2.state也就是组件自己的状态。3. 事件处理函数例如点击、输入、提交。4. 接口返回值和列表渲染数据这也是前端项目里最常见的一部分。十九、React 预习 1组件 props 为什么特别适合配 TypeScript先看一个非常简化的例子。interface WelcomeCardProps { userName: string; age: number; } function WelcomeCard(props: WelcomeCardProps) { return ( div h2{props.userName}/h2 p{props.age} 岁/p /div ); }1. 这里的 TypeScript 在帮什么忙它在帮你明确这个组件到底需要什么数据才能正常使用2. 为什么这特别有价值因为组件一旦开始复用最容易出问题的就是少传数据传错类型字段名写错而 props 类型正好可以把这些规则写清楚。二十、React 预习 2组件 state 为什么也很值得补类型例如你可能会看到这样的状态interface UserInfo { userName: string; age: number; } const currentUser: UserInfo | null null;或者在真正 React 写法里你以后会接触到类似const [currentUser, setCurrentUser] useStateUserInfo | null(null);1. 这段代码最想说明什么不是让你现在就去背useState。而是先让你知道前端组件状态很常出现“还没数据”和“拿到数据后有结构”的两阶段状态这正是 TypeScript 特别能发挥作用的地方。2. 为什么这对后面的 React 学习很重要因为你会越来越频繁地处理初始为空加载中请求成功后再赋值这种状态变化。二十一、React 预习 3事件类型为什么会出现再往前一步你以后在 React 项目里还会看到事件类型。例如function handleInputChange(event: React.ChangeEventHTMLInputElement): void { console.log(event.target.value); }当前阶段你不用去记完整写法。你只要先建立一个很重要的认知React 里的事件对象也是有类型的这意味着不是所有事件对象都一样输入框事件和按钮点击事件不一样TypeScript 会帮你把事件对象的结构提示得更清楚二十二、为什么很多人会觉得“项目里用了 TypeScript好像一下子报错变多了”这其实是个非常真实的感受。而且它不代表你变差了。很多时候真正发生的是原本就存在的问题现在终于被更早暴露出来了1. 为什么会有这种感觉因为以前很多问题是运行时才发现页面某个角落才暴露某个接口返回后才炸而现在 TypeScript 把它们往前提了。2. 这是一件好事吗从开发质量角度看通常是好事。因为越早发现问题修复成本通常越低。3. 所以更好的理解方式是什么不是“用了 TypeScript 以后问题变多了”而是“用了 TypeScript 以后我更早看见问题了”二十三、把 TypeScript 放进项目时最稳的工作流是什么这一节你可以直接当成一个入门工作流。1. 第一步先看数据模型例如用户对象商品对象列表项对象表单对象2. 第二步再看函数边界也就是参数是什么返回什么3. 第三步再看页面状态和接口返回值因为它们直接决定很多页面逻辑的稳定性。4. 第四步最后再修零散局部问题例如某个小变量类型某个局部函数的小细节5. 为什么这套顺序更稳因为它遵循的是先抓核心结构再抓边缘细节这会比“看到哪条红线就随手补哪条”稳很多。二十四、初学者最容易踩的坑这一章虽然不以新语法为主但坑其实非常多而且都很真实。1. 一上来就钻进配置细节里你现在当然可以知道有tsconfig.json有一些配置项但当前阶段更重要的不是背配置而是先知道 TypeScript 在项目里最核心地服务什么也就是数据结构函数规则页面状态编辑器提示2. 只想“消灭红线”不想“理解数据”这是非常常见的风险。要记住真正要修的不是红线本身而是红线背后的结构问题3. 迁移时从最边缘的小变量开始补类型这会让你很快失去方向。更稳的顺序永远是先看核心数据结构和函数边界4. 一上来就追求特别完美、特别花的类型写法现在完全没必要。当前阶段最重要的是让项目核心数据结构清楚、函数输入输出清楚、状态边界清楚5. 一看到 React 里的类型就慌其实你现在已经在做准备了。你前面学过的接口联合类型函数类型泛型这些都会在 React 里继续出现。也就是说React 里的 TypeScript 并不是一套完全陌生的新世界而是你已经学过的类型能力换了一个更真实的项目场景继续用二十五、本章实践练习这一章最适合做的不是继续背更多语法而是开始把 TypeScript 放回项目视角里理解。1. 练习 1把一个 JavaScript 小项目迁移为 TypeScript 版本你可以从前面写过的小项目里任选一个例如待办事项应用个人作品页里的交互小功能表单输入与展示小练习迁移时建议按这个顺序来先补核心对象结构再补列表类型再补关键函数参数与返回值最后再修零散报错这个练习重点是训练你用结构化顺序做迁移而不是哪里红了就乱改哪里2. 练习 2记录并修复三类最常见的类型错误建议你在迁移过程中至少主动记录这三类错误类型不匹配对象缺少字段可能为undefined或null然后分别写下报错是什么意思你是怎么定位的你最后怎么修的这个练习重点是训练不只是修过去还要学会总结报错模式3. 练习 3给一个接口返回值结构补上类型请你自己模拟一个接口返回值例如interfaceApiResponseT{code:number;message:string;data:T;}然后分别写出返回单个对象的版本返回数组的版本这个练习重点是帮助你把泛型 接口数据结构 项目场景真正连起来。4. 练习 4尝试为一个 React 组件写 props 类型即使你还没正式开始学 React也可以先做个非常简单的预习练习。例如interface UserCardProps { userName: string; age: number; }这个练习重点不是学 React 本身而是提前建立组件其实也在接收一种结构明确的数据二十六、学习重点提示这一章最重要的不是去背一堆项目配置而是先真正看见TypeScript 在真实前端项目里到底是怎么帮你工作的请重点记住项目里的 TypeScript重点不只是“代码能不能跑”而是“开发过程能不能更稳”。.ts通常写普通 TypeScript 逻辑.tsx通常写包含 JSX 的文件。tsconfig.json本质上是在定义项目里的 TypeScript 检查和处理规则。编辑器提示、自动补全和类型报错是 TypeScript 在项目里最直接的价值体现之一。看报错时先抓文件、行号、期待类型、实际类型。迁移项目时先补核心数据结构、函数边界、接口返回值和状态类型。React 项目里的 TypeScript最常先出现在 props、state、事件和接口数据上。不要一开始追求复杂炫技的类型写法先把项目核心结构写清楚。如果你只记一句话请记住在真实前端项目里TypeScript 最重要的价值不是多写几行类型而是让数据结构、函数边界和改动影响都更早变得清楚。二十七、本章小结这一章我们把前面学过的 TypeScript 基础真正接到了项目视角里。你已经理解了TypeScript 在前端项目中的基本工作流程.ts、.tsx和tsconfig.json大概分别在做什么编辑器提示和类型报错在项目里的价值最常见几类类型错误该怎么读、怎么想、怎么修一个 JavaScript 小项目迁移到 TypeScript 的更稳顺序前端项目里最值得优先补类型的几个位置React 项目里最常见的 TypeScript 预习场景更重要的是你开始建立一种很关键的工程意识TypeScript 不是孤立知识点而是整个项目开发体验和代码稳定性的一部分这一步非常关键。因为从这里开始你已经具备进入下一阶段综合实战的基础了。二十八、课后思考题请你思考下面几个问题为什么说项目里的 TypeScript重点不只是“能不能运行”而是“开发过程能不能更稳”.ts和.tsx的区别本质上和什么有关为什么tsconfig.json不需要一上来全背但又必须知道它在项目里扮演什么角色为什么迁移项目时应该先补核心数据结构而不是先从边角变量开始为什么“对象可能为空”这类报错在真实项目里特别有价值为什么说 React 里的 TypeScript并不是一套完全陌生的新世界建议你用自己的话把答案写下来。只要你能把这些问题说清楚说明你已经真正开始把 TypeScript 从“语法知识”推进到“项目能力”了。二十九、下一篇预告接下来我们会进入第三阶段综合实战从零开始学前端 | 第三阶段综合实战带类型约束的学生管理小工具到那时你会把这一阶段学过的内容真正串起来基础类型接口与类型别名函数类型泛型基础也就是说下一篇我们不再只讲单点知识而是开始真正做一个用 TypeScript 去约束数据结构和功能逻辑的小项目