函数、对象与接口如果说基础类型只是建立了“值有边界”这件事那么函数和对象才是 TypeScript 真正开始发挥工程价值的地方。因为现实项目里的复杂度大部分都不是来自一个孤立的string或number而是来自“一个函数到底接收什么、返回什么”“一个对象到底有哪些字段、哪些字段可以没有、哪些字段绝对不能被改”。换句话说真正让 TypeScript 变得有用的不是你会不会声明一个变量的类型而是你能不能用类型把代码中的契约说清楚。函数类型的核心不是语法而是契约先看最基础的函数类型写法functionadd(a:number,b:number):number{returnab;}这里看起来只有两个小细节参数类型a: number、b: number返回值类型: number但从工程角度看这已经是一份很明确的函数契约。它告诉调用方你必须传两个数字我只会返回一个数字如果你传字符串或者期待它返回别的结构那是错误用法这正是 TypeScript 在函数层面的作用。它让函数不再只是“你去读代码才知道干什么”而是签名本身就能传递大部分必要信息。参数类型几乎总该明确写出来局部变量可以多用推断函数参数通常不建议省略。因为参数是边界是外部进入当前逻辑的入口。边界模糊后续类型链条就会一起变模糊。functioncreateUser(name:string,age:number){return{id:Date.now(),name,age};}你也许会说函数返回值这里没写类型。确实返回值在很多简单场景里可以依赖推断。但参数最好尽量明确因为它们直接定义了调用协议。返回值类型什么时候该显式标注返回值不是每次都必须手写但在以下场景里显式标注往往更值得核心业务函数对外导出的公共函数返回结构复杂的函数不希望实现细节意外改变对外契约时例如typeCreateUserResult{id:number;name:string;age:number;};functioncreateUser(name:string,age:number):CreateUserResult{return{id:Date.now(),name,age};}这样做的好处是如果某天有人把id改成了字符串或者返回结构被悄悄修改TypeScript 会立即提醒你。可选参数、默认参数和剩余参数可选参数functiongreet(name:string,title?:string){returntitle?${title}${name}:name;}title?: string表示这个参数可以不传。注意“可选”不是“随便乱传”它的含义是这个位置要么不存在要么是string。默认参数functioncreatePage(pageSize:number10){return{pageSize};}默认参数让函数更好用但它本质上仍然是一个明确的输入边界而不是放弃约束。剩余参数functionsum(...nums:number[]):number{returnnums.reduce((total,current)totalcurrent,0);}剩余参数在工具函数、事件处理、转发函数里很常见。关键点是即便参数个数可变元素类型依然应该清楚。对象类型是业务建模的真正起点大部分业务代码最终都绕不开对象。用户、订单、配置、组件 props、接口响应本质上几乎都是对象结构。constuser:{id:number;name:string;active:boolean;}{id:1,name:Alice,active:true};这种内联写法在小范围里没问题但一旦结构重复出现就应该提取出来。原因很简单重复结构意味着重复维护而重复维护迟早会失控。type和interface都能描述对象但思维略有不同你可以这样写typeUser{id:number;name:string;active:boolean;};也可以这样写interfaceUser{id:number;name:string;active:boolean;}初学阶段两者最重要的区别不是语法能力而是使用语义interface更像“对象契约”type更像“类型表达式的别名”如果你描述的是一个清晰的对象结构interface很自然如果你要组合联合类型、函数类型、元组、映射类型type往往更灵活。在没有团队规范时我个人更建议对象边界优先用interface组合类型、工具型类型优先用type这不是绝对规则但这种分工通常更利于阅读。函数类型也应该被当成一等公民很多项目里函数不只是“实现逻辑”它本身也经常作为参数、配置项或策略注入出现。这时函数类型就很值得抽离typeFormatter(value:string)string;constupperCase:Formatter(value)value.toUpperCase();consttrimText:Formatter(value)value.trim();你会在这些场景里频繁见到这种写法数组方法回调表单校验器组件事件回调中间件策略模式当一个函数类型会被复用单独给它命名比反复写长签名清晰得多。可选属性和只读属性是表达业务语义的重要方式interfaceProduct{readonlyid:number;name:string;description?:string;}这里的两个小语法实际项目里非常有价值。readonlyreadonly不是为了防止程序员手滑它表达的是业务语义这个值一旦生成就不应该被重新赋值。比如数据库主键、创建时间、订单编号这些字段很适合只读。?可选属性不是“这个字段可以乱来”而是“这个字段在合法数据里允许不存在”。这对于头像、备注、简介、扩展信息等很常见。一个类型写得好不好很大程度上就看这些边界有没有说清楚。一个更接近真实项目的例子interfaceUserProfile{readonlyid:number;name:string;email:string;avatar?:string;bio?:string;}functionupdateUserName(user:UserProfile,nextName:string):UserProfile{return{...user,name:nextName};}从这个例子里你应该能感受到类型系统的意义不是限制你而是帮你把对象的合法形态表达得更稳定id不能乱改name和email是核心字段avatar和bio可以没有这类结构一旦清楚后面的函数、接口、组件都更容易围绕它工作。一个工程判断标准类型有没有把业务意图说出来写 TypeScript 时别只问“这样能不能通过编译”还要问这个字段是否应该只读这个属性是否真的可选这个函数的参数是不是表达了真实前提这个返回值是不是把外部契约说清楚了如果你的类型只是“勉强让编辑器不报错”那它的价值会很有限。真正好的类型是读者不看实现细节也能大概理解这段代码能做什么、不能做什么。常见误区误区一对象一复杂就直接any这会让最该被建模的部分恰好失去保护。复杂对象更应该拆清楚不该直接逃避。误区二所有返回值都不写类型简单局部函数可以省但公共函数和核心逻辑如果完全依赖推断后续实现变化时更容易无意中破坏契约。误区三把type和interface之争当成重点真正重要的不是你站哪一派而是你能不能稳定地写出清晰的结构定义。语义一致、团队一致通常比“理论上谁更优雅”更重要。本文小结函数、对象与接口是 TypeScript 建模的主战场。基础类型解决的是“一个值是什么”函数和对象解决的是“系统如何协作、边界如何表达”。你如果能把函数契约写清楚、把对象结构建模准确就已经掌握了 TypeScript 最有实际价值的一部分。练习给一个“创建用户”的函数补上参数类型和返回值类型并思考哪些字段应该由调用方传入哪些字段应该由系统生成。用interface定义一个Book包含只读id、必填title、可选author、可选description。写一个函数类型Validator表示接收字符串并返回布尔值然后实现两个不同的校验函数。后记2026年5月21日于上海。
【Typescript】03-函数对象与接口
发布时间:2026/5/22 6:20:12
函数、对象与接口如果说基础类型只是建立了“值有边界”这件事那么函数和对象才是 TypeScript 真正开始发挥工程价值的地方。因为现实项目里的复杂度大部分都不是来自一个孤立的string或number而是来自“一个函数到底接收什么、返回什么”“一个对象到底有哪些字段、哪些字段可以没有、哪些字段绝对不能被改”。换句话说真正让 TypeScript 变得有用的不是你会不会声明一个变量的类型而是你能不能用类型把代码中的契约说清楚。函数类型的核心不是语法而是契约先看最基础的函数类型写法functionadd(a:number,b:number):number{returnab;}这里看起来只有两个小细节参数类型a: number、b: number返回值类型: number但从工程角度看这已经是一份很明确的函数契约。它告诉调用方你必须传两个数字我只会返回一个数字如果你传字符串或者期待它返回别的结构那是错误用法这正是 TypeScript 在函数层面的作用。它让函数不再只是“你去读代码才知道干什么”而是签名本身就能传递大部分必要信息。参数类型几乎总该明确写出来局部变量可以多用推断函数参数通常不建议省略。因为参数是边界是外部进入当前逻辑的入口。边界模糊后续类型链条就会一起变模糊。functioncreateUser(name:string,age:number){return{id:Date.now(),name,age};}你也许会说函数返回值这里没写类型。确实返回值在很多简单场景里可以依赖推断。但参数最好尽量明确因为它们直接定义了调用协议。返回值类型什么时候该显式标注返回值不是每次都必须手写但在以下场景里显式标注往往更值得核心业务函数对外导出的公共函数返回结构复杂的函数不希望实现细节意外改变对外契约时例如typeCreateUserResult{id:number;name:string;age:number;};functioncreateUser(name:string,age:number):CreateUserResult{return{id:Date.now(),name,age};}这样做的好处是如果某天有人把id改成了字符串或者返回结构被悄悄修改TypeScript 会立即提醒你。可选参数、默认参数和剩余参数可选参数functiongreet(name:string,title?:string){returntitle?${title}${name}:name;}title?: string表示这个参数可以不传。注意“可选”不是“随便乱传”它的含义是这个位置要么不存在要么是string。默认参数functioncreatePage(pageSize:number10){return{pageSize};}默认参数让函数更好用但它本质上仍然是一个明确的输入边界而不是放弃约束。剩余参数functionsum(...nums:number[]):number{returnnums.reduce((total,current)totalcurrent,0);}剩余参数在工具函数、事件处理、转发函数里很常见。关键点是即便参数个数可变元素类型依然应该清楚。对象类型是业务建模的真正起点大部分业务代码最终都绕不开对象。用户、订单、配置、组件 props、接口响应本质上几乎都是对象结构。constuser:{id:number;name:string;active:boolean;}{id:1,name:Alice,active:true};这种内联写法在小范围里没问题但一旦结构重复出现就应该提取出来。原因很简单重复结构意味着重复维护而重复维护迟早会失控。type和interface都能描述对象但思维略有不同你可以这样写typeUser{id:number;name:string;active:boolean;};也可以这样写interfaceUser{id:number;name:string;active:boolean;}初学阶段两者最重要的区别不是语法能力而是使用语义interface更像“对象契约”type更像“类型表达式的别名”如果你描述的是一个清晰的对象结构interface很自然如果你要组合联合类型、函数类型、元组、映射类型type往往更灵活。在没有团队规范时我个人更建议对象边界优先用interface组合类型、工具型类型优先用type这不是绝对规则但这种分工通常更利于阅读。函数类型也应该被当成一等公民很多项目里函数不只是“实现逻辑”它本身也经常作为参数、配置项或策略注入出现。这时函数类型就很值得抽离typeFormatter(value:string)string;constupperCase:Formatter(value)value.toUpperCase();consttrimText:Formatter(value)value.trim();你会在这些场景里频繁见到这种写法数组方法回调表单校验器组件事件回调中间件策略模式当一个函数类型会被复用单独给它命名比反复写长签名清晰得多。可选属性和只读属性是表达业务语义的重要方式interfaceProduct{readonlyid:number;name:string;description?:string;}这里的两个小语法实际项目里非常有价值。readonlyreadonly不是为了防止程序员手滑它表达的是业务语义这个值一旦生成就不应该被重新赋值。比如数据库主键、创建时间、订单编号这些字段很适合只读。?可选属性不是“这个字段可以乱来”而是“这个字段在合法数据里允许不存在”。这对于头像、备注、简介、扩展信息等很常见。一个类型写得好不好很大程度上就看这些边界有没有说清楚。一个更接近真实项目的例子interfaceUserProfile{readonlyid:number;name:string;email:string;avatar?:string;bio?:string;}functionupdateUserName(user:UserProfile,nextName:string):UserProfile{return{...user,name:nextName};}从这个例子里你应该能感受到类型系统的意义不是限制你而是帮你把对象的合法形态表达得更稳定id不能乱改name和email是核心字段avatar和bio可以没有这类结构一旦清楚后面的函数、接口、组件都更容易围绕它工作。一个工程判断标准类型有没有把业务意图说出来写 TypeScript 时别只问“这样能不能通过编译”还要问这个字段是否应该只读这个属性是否真的可选这个函数的参数是不是表达了真实前提这个返回值是不是把外部契约说清楚了如果你的类型只是“勉强让编辑器不报错”那它的价值会很有限。真正好的类型是读者不看实现细节也能大概理解这段代码能做什么、不能做什么。常见误区误区一对象一复杂就直接any这会让最该被建模的部分恰好失去保护。复杂对象更应该拆清楚不该直接逃避。误区二所有返回值都不写类型简单局部函数可以省但公共函数和核心逻辑如果完全依赖推断后续实现变化时更容易无意中破坏契约。误区三把type和interface之争当成重点真正重要的不是你站哪一派而是你能不能稳定地写出清晰的结构定义。语义一致、团队一致通常比“理论上谁更优雅”更重要。本文小结函数、对象与接口是 TypeScript 建模的主战场。基础类型解决的是“一个值是什么”函数和对象解决的是“系统如何协作、边界如何表达”。你如果能把函数契约写清楚、把对象结构建模准确就已经掌握了 TypeScript 最有实际价值的一部分。练习给一个“创建用户”的函数补上参数类型和返回值类型并思考哪些字段应该由调用方传入哪些字段应该由系统生成。用interface定义一个Book包含只读id、必填title、可选author、可选description。写一个函数类型Validator表示接收字符串并返回布尔值然后实现两个不同的校验函数。后记2026年5月21日于上海。