资讯动态

深入理解 TypeScript 编码实践:减少 setter 属性的使用,以显式方法替代隐式赋值

发布时间:2026/10/8 14:03:21 来源:尧图企业网站定制
文档教程【免费下载链接】typescript-book-chineseTypeScript Deep Dive 中文版项目地址https://gitcode.com/gh_mirrors/ty/typescript-book-chinese点击查看免费下载本文基于《深入理解 TypeScript》typescript-book-chinesetips 章节中的《减少 setter 属性的使用》展开。该文讨论的是一个非常实际且常被忽视的类设计问题在 TypeScriptJavaScript的class中setter/getter存取器与显式的setBar(value)/getBar()方法该如何取舍。读完本文你将理解为什么面向复合赋值的 setter 会损害代码可读性、显式方法在可维护性上有哪些具体优势以及什么场景下才值得继续使用存取器并掌握与仓库内「函数参数」「对象字面量惰性初始化」等技巧配套的落地方案。问题场景一行赋值背后藏着的副作用先看原文档给出的核心示例。假设业务代码中出现了这样一段赋值foo.bar { a: 123, b: 456 };而Foo类的内部实现长这样class Foo { a: number; b: number; set bar(value: { a: number; b: number }) { this.a value.a; this.b value.b; } } let foo new Foo();第一眼看上去foo.bar {...}只是「把属性bar赋了个新值」简单、无害。但事实并非如此由于Foo定义了set bar这一行赋值实际触发了 setter 内部逻辑会同时修改foo的a和b两个字段。原文档对此的结论非常明确这并不是 setter 的一个好的使用场景。当开发人员阅读第一段代码时他并不知道将要被更改的所有内容的上下文。相反如果开发者写成foo.setBar(value)函数调用本身的语义就会提醒读者foo内部可能发生一些改变。这正是「减少 setter 属性的使用」这一技巧的核心——让“会改变内部状态”这个事实在调用点变得可见。存取器语法基础setter 与 getter 在 TypeScript 中如何工作要深入讨论取舍先明确 TypeScript 中的存取器语法。TypeScript 的class原生支持 ES5 的 accessor 语法通过get/set关键字声明class Foo { private _bar: { a: number; b: number }; // 取值器读取时被调用 get bar() { return this._bar; } // 赋值器赋值时被调用 set bar(value: { a: number; b: number }) { this._bar value; } }要点如下get与set共用同一个属性名这里是bar二者组合后对外表现为一个普通属性只声明get时属性是只读的赋值会报错只声明set时属性是只写的读取会报错set bar(value: T)的参数类型即属性可被赋予的类型value参数由赋值运算符右侧的值隐式传入调用方无法命名它也无法额外传参存取器的类型注解需要满足 TypeScript 的类型兼容性规则赋值类型必须能被 setter 参数类型接收。从上面的例子可以直观看出问题通过foo.bar {...}赋值时赋值的目标是属性名而不是方法名。读代码的人看到的是一处属性写入但实际执行的却是一段可能包含任意逻辑的函数体。为什么显式方法更优四层可读性分析原文档认为foo.setBar(value)优于foo.bar value我们可以把这背后的理由展开为四个维度1. 调用点语义清晰foo.bar value是一个「赋值语句」语义上是纯数据写入而foo.setBar(value)是一个「方法调用」语义上天然暗示「可能触发内部逻辑」。正如原文档所说开发者在使用foo.setBar(value)时会意识到在foo里可能会引起一些改变于是会更谨慎地审查副作用。2. 复合赋值与副作用透明化原文档示例中setter 把一次赋值拆解为对a、b两个字段的写入——这属于典型的复合赋值。此外 setter 还经常被用来承载校验、归一化、事件通知等逻辑class Foo { set bar(value: { a: number; b: number }) { if (value.a 0) { throw new Error(a 不能为负数); } this.a value.a; this.b value.b; this.onChanged(); // 触发内部通知 } }校验和副作用本身没有错但当它们被藏进赋值背后时调用方完全无感。改用显式方法后class Foo { setBar(value: { a: number; b: number }) { if (value.a 0) { throw new Error(a 不能为负数); } this.a value.a; this.b value.b; this.onChanged(); } }foo.setBar(...)让读者以及 IDE 的调用链分析、断点调试能顺着方法名找到逻辑入口。3. 可调试与可测试性调试在set bar(...)中打断点只有在赋值语句执行时才会命中且无法从调用栈上区分是「哪一次赋值」而setBar的名字与调用点一一对应日志与堆栈信息更可读。测试显式方法可以像普通方法一样被单独测试、被 mock而存取器需要借助「赋值」来触发测试意图不明显。4. 与函数式风格的一致性本仓库 docs/tips/functionParameters.md 讨论了另一个相关技巧当函数参数过多或类型相同时建议改为接收一个对象参数foo({ flagA, flagB })因为调用形式的可读性决定了错误能否被及时发现。显式方法正是把这一思想延伸到类成员上——参数以对象形式传入方法名表达意图类型注解保证安全class Foo { setBar(config: { a: number; b: number }) { this.a config.a; this.b config.b; } }而 getter 侧同理显式的getBar()比foo.bar更清楚地表达「这是一个可能触发计算/缓存逻辑的读取操作」。setter 的适用边界什么时候可以保留「减少 setter 属性的使用」不等于「禁用 setter」。结合原文档的观点可以总结出如下取舍标准场景建议理由setter 仅做简单的一对一赋值纯数据封装可以保留无副作用读起来仍是「属性赋值」的直觉setter 内部涉及复合字段写入、校验、换算、通知改用显式方法副作用需要被调用点看见getter 是纯计算、无副作用可以保留例如get fullName() { return this.first this.last; }getter 内部有缓存、异步或昂贵计算改用显式方法读取语义会误导调用方对性能的预期判断标准可以浓缩为一句话如果get/set内部只是读取或写入「同一个字段」存取器是合适的只要内部逻辑超出了“同名同字段”的范畴就应显式化。另外要注意存取器与字段同名时容易引起无限递归典型的错误写法是在 getter 内直接返回同名属性。把内部数据放在下划线私有字段_bar上是常见规避方式但如果逻辑一复杂显式方法显然更省心。配套实践来自本仓库 tips 章节的关联建议「减少 setter 属性」并不是孤立的一条编码规范它与本仓库 tips 章节的多篇文章互相呼应对象字面量的惰性初始化docs/tips/lazyObjectLiteralInitialization.mdlet foo {}; foo.bar 123;这种「先建空对象、后补属性」的写法在 TypeScript 中会因类型推断报错。它从另一个侧面说明逐个补属性的赋值式编程本身就是脆弱的更稳妥的做法是构造时就一次性提供完整结构。这恰好与「不要用 setter 拆字段赋值」一脉相承——数据结构的完整性应当在入口处保证。函数参数对象化docs/tips/functionParameters.md参数过多时改为接收对象foo({ flagA, flagB })与本文setBar({ a, b })的推荐写法完全同构两者都在强调「调用形式的可读性」。类是有用的docs/tips/classAreUseful.md该文建议用class组织内部状态与初始化逻辑。一旦选择 class 承载状态成员对外暴露的方式方法 vs 存取器就决定了这类状态的可控性——显式方法让状态变更路径更可追踪。静态构造函数docs/tips/staticConstructors.md该文展示了用MyClass.initalize()显式触发初始化。这与本文的结论一致在 TypeScript/JavaScript 中凡是会「改变对象内部状态」的动作用命名方法显式表达比依赖隐式语法钩子更符合可维护性直觉。总结把「可能引起改变」写进调用点回到原文档的核心主张本文的全部讨论可以收敛为一句实践准则倾向于使用更精确的set/get函数如setBar、getBar减少使用setter/getter。具体落地时建议在团队代码规范中明确setter/getter 仅用于无副作用的简单属性封装涉及复合字段、校验、换算、通知等逻辑时一律使用setBar(value)形式的显式方法对外暴露状态变更的 API优先设计为方法而非赋值让「可能引起内部改变」的信号出现在调用点结合 函数参数对象化 的做法为多字段更新提供结构化的参数对象兼顾类型安全与可读性。这样当后来者包括未来的你自己读到foo.setBar({ a: 123, b: 456 })时不需要翻看类的实现就已经知道这一次调用可能会改变foo内部的某些东西。而这正是原文档想传达的最重要的编码直觉。赞分享文档教程【免费下载链接】typescript-book-chineseTypeScript Deep Dive 中文版项目地址https://gitcode.com/gh_mirrors/ty/typescript-book-chinese点击查看免费下载相关推荐限制 TypeScript 属性 setter 的使用优先显式 setBar/getBar 函数的设计实践限制 TypeScript 属性 setter 的使用优先显式 setBar/getBar 函数的设计实践 本文基于 TypeScript Deep Dive教程深入理解torchdiffeq中的显式与隐式Adams方法线性多步法的完整实现指南深入理解torchdiffeq中的显式与隐式Adams方法线性多步法的完整实现指南 torchdiffeq是一个强大的PyTorch微分方程求解库专门用于解深度学习科学计算使用显式组件变体替代布尔属性next-shadcn-dashboard-starter 中的 React 组合模式实践使用显式组件变体替代布尔属性next shadcn dashboard starter 中的 React 组合模式实践 在复杂 React 组件中 isTh前端UI组件上一篇揭秘Barlow字体设计低对比度与微圆角如何提升可读性下一篇OpenResume表单状态恢复用户会话与历史记录创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑