You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
functionassertIsNumber(value: unknown): asserts value is number{if(typeofvalue!=='number'){thrownewError('Not a number!')}}functiondoSomething(x: unknown){// 这里 x 是 unknownassertIsNumber(x)// 通过断言函数后, x 就变成了 numberconsole.log(x.toFixed(2))}
这和“类型谓词” ((x: unknown): x is number) 类似,但 asserts 更偏向“抛错式”断言。
typeIf<Condextendsboolean,TrueType,FalseType>=// Trick: if Cond is true => never extends false. If Cond is false => never extends true.// 当然还要配合分发写法Condextendstrue ? TrueType : FalseType
functionprocessData<Textends{a: number;b: string;nested: {x: boolean}}>({
a,
b,nested: { x }}: T){// 这里 a, b, x 都是正确类型console.log(a.toFixed(),b.toUpperCase(),x.valueOf())}// 虽然看似我们给 T 做了固定的结构,但在大型场景里可以使用// T extends SomeGenericConstraint to keep it open for more fields.
interfaceApiResponse{data: anystatus: number}// 可以声明合并interfaceApiResponse{message?: string}// 高级条件处理用 typetypeExtractData<TextendsApiResponse>=T['data']extends infer D ? D : never
下面再补充一些更“高阶”、更有挑战性的 TypeScript 技巧,涵盖了声明合并、模块扩展、类型层面做逻辑运算与数据结构转换、模板字面量进阶玩法等方面,希望能进一步激发你对“类型编程”的兴趣。
1. 模块扩展(Module Augmentation)
有时我们需要给第三方库或已有模块添加额外类型、方法或属性,比如想给
express
的Request
对象添加一个自定义字段。这就需要用到模块扩展(Module Augmentation)。在d.ts
声明文件或全局声明的环境下使用declare module 'xxx'
来完成。tsconfig.json
中,"skipLibCheck"
或"allowJs"
等选项不会导致对声明文件的忽略,否则可能无法生效。4. 声明合并(Declaration Merging)与 Interface vs. Type 的混合技巧
4.1 声明合并
TypeScript 中,对同名的
interface
进行多次声明时,会发生声明合并(Declaration Merging)。有些时候我们需要刻意利用这种特性来拆分大型类型,或者给现有类型打补丁。4.2 Interface 与 Type 混合
interface
支持声明合并,但不支持条件类型、内部infer
等。type
更灵活,能写条件类型、联合、交叉等高级玩法,但不支持声明合并。在大型项目中,常常会同时使用
interface
和type
来取长补短。比如主体结构用interface
,需要高级条件处理、模板字面量时再用type
进行扩展或转换。6. 复合泛型:让类型工具串联起来
很多时候,单独一个条件类型或映射类型不足以应付更复杂的变换,需要将多个泛型工具串联使用,形成“管道”的效果。比如先过滤键,再把剩余的键都变成只读,最后对某些字段做模板转换。
一个可能的示例思路:
只要定义好每个“环节”的工具类型,最终再组合使用,就能在类型层面随心所欲地“变形”你的数据结构。
7. 类型层面的“缓存”与“记忆化”技巧(Type Memoization)
当做复杂的递归类型变换时,可能遇到编译性能问题或递归深度过大的问题。部分社区开发者会利用**类型层面的“映射表”**或“内存对象”来缓存已计算过的类型结果,从而降低递归次数(这并非官方提供的功能,而是一种“hack”思路)。
8. 利用 “Branded Type + 交叉” 实现 Opaque Type
“Branding” (或称 Opaque Type)是一种在 TS 中创建逻辑上不可互换的标识类型的常用技巧。
更进一步的用法是利用交叉将品牌信息藏起来,在外部不可访问,但依然能在类型检查时进行区分。
number
,但编译器会识别到它们带有不同的品牌而拒绝错误用法,从而避免把人民币当美元来支付。9. 使用
symbol
做更安全的对象键TypeScript 对
symbol
键也提供了良好的支持,可以用于增强你的对象接口,防止与常规字符串键发生冲突。symbol
来保证一定程度的“不可冲突”且仍有静态类型提示。10. 复杂函数的重载 + 泛型推断 + 条件约束
当函数的参数签名非常多元化,需要根据不同的参数类型返回不同的结果类型时,可能单纯的“联合 + 类型守卫”还不够灵活,就要结合重载声明与内部条件类型,并在实现中推断与区分。
1. 利用条件类型做“模式匹配”(Pattern Matching)
虽然 TS 不直接支持类似 Scala / F# / Rust 那种“模式匹配”,但我们可以在类型层面通过联合类型 + 条件类型 + 类型谓词,模拟出部分“模式匹配”的效果。例如,对一个联合类型进行分支解析:
payload
。2. 利用
asserts
关键字声明自定义“断言函数”自 TS 3.7 起,TypeScript 引入了
asserts condition
这种声明方式,可以告诉编译器:如果断言函数正常返回(未抛出),则表明传入的值满足某种条件。这在测试或校验阶段非常有用。(x: unknown): x is number
) 类似,但asserts
更偏向“抛错式”断言。5. 利用“条件类型 + 联合类型”对函数参数做类型分解
有时候我们需要一个函数能根据参数不同,返回不同的类型。如果不想写多重重载声明(太多签名),可以在一个函数里结合联合类型 + 条件类型进行内部分支,比如:
6. 使用 “
type
extends never” 做静态分支判断有时想在类型层面做“布尔条件”,并返回不同类型,但 TS 并没有布尔型的
true
/false
这种类型可以直接操作。可以利用“T extends never
”是否为true
的特性来模拟:或者一些社区的更“黑科技”写法,会用“
never
extends 'x'”之类的比较,模拟“真假判断”。这类技巧非常晦涩,除非你在写一个复杂的类型工具库,否则不建议大规模使用。7. 巧用
strictBindCallApply
提升函数调用的类型安全在
tsconfig.json
中启用"strictBindCallApply": true
后,TypeScript 会对Function.prototype.bind/call/apply
做更严格的类型检查。obj.fn.call(context, arg1, arg2)
或obj.fn.apply(context, [args...])
时,TS 会自动推断并校验context
类型是否匹配this
,以及参数是否匹配fn
的形参类型。8. 泛型中使用
this
类型,实现“链式”方法的类型推断在写一些“链式 API”时,我们可能想在基类里声明一个返回
this
的方法,让派生类也能保持正确的类型。可以通过“this
类型”或“this
泛型”实现:9. 在 React 中处理“ForwardRef + 泛型组件”
在 React 中编写泛型组件或需要转发 Ref(
forwardRef
)时,有时候类型比较复杂。可以使用React.ForwardRefRenderFunction
或React.ForwardRefExoticComponent
等内置类型,并把泛型也穿进去。例如:as <T>(props: ...) => ...
来保留泛型参数,配合React.forwardRef
。10. “Strip out never” 操作、在元组或数组里排除不需要的类型
当把多个类型条件组合到一个数组或元组中时,可能会产生
never
;如果我们想把那些never
剔除,可以先把它们变成“可选项”再过滤。例如:2. 自定义泛型“推导约束”让参数更灵活(推断 + 约束二合一)
在某些场景下,我们想让函数参数在被推断为泛型类型时,同时还要对其再进行一些约束。例如:
T
必须是number
或string
,但是我们也接受输入T
或者T[]
,并最终返回T[]
。T extends number | string
这种写法,让 TS 可以在推断的同时保证类型正确。3. 使用
keyof any
创建可索引类型 (string | number | symbol
)在 TypeScript 里,
keyof any
会得到string | number | symbol
,这是对象可用作键的所有类型合集。有时想做一个可以用作各种键的“字典”结构,可以这样写:Record<string, T>
或Record<PropertyKey, T>
就够了。keyof any
这个小技巧在编写类型工具时有时能派上用场。4. 在“对象”与“元组”之间做类型变换
有时我们需要把对象的每个属性变成一个数组/元组元素,或者反之将元组/数组的元素转换成对象的键。通过映射类型 + 索引访问,可以做“对象 <-> 元组”之间的结构转换。
4.1 对象转元组
这个类型本身得到一个联合类型,你可以再进一步将其收敛成数组或做其他操作。
4.2 元组转对象
如果你有一个键值对形式的元组,可以再用映射类型“重映射键”,把它变成对象结构:
5. “泛型”与“异或类型 (XOR Type)” —— 保证只能传一组属性
在业务里有时会遇到这样的需求:只能在一组属性中选其中一组,但不允许多选,也不允许都不选。可以自定义一个 XOR(exclusive or)类型来实现此逻辑。例如,你要么传
email
要么传phone
,不允许同时存在或都不存在。6. React 中声明事件类型的“提取 + 推断”,避免手动书写繁琐的 DOM 事件类型
在 React 项目中,我们常常写
React.MouseEvent<HTMLDivElement>
之类的类型,但在一些场景下我们可以自动推断。比如:JSX.IntrinsicElements['button']
等写法来获得原生<button>
元素的类型定义,避免重复。7. “Intersection”+“Mapped”+“Generic” 三连,做极度灵活的校验或配置
在某些场景,需要把多个相似的配置接口“合并”到一起,还想在属性层面做一部分处理,就可以用交叉类型(intersection)与映射类型配合。例如,我们有多个插件配置,每个插件都有自己的格式:
现在想写一个“总的配置”,自动收敛成
{ 'plugin-a': PluginAConfig; 'plugin-b': PluginBConfig; ... }
之类的结构:8. 在
enum
与“字面量联合”之间做转换枚举
enum
是 TypeScript 的一大特色,但有时我们不想直接使用enum
,更倾向使用字面量联合(因为编译后不会多出额外的 JS 代码)。如果你需要在类型层面做二者的互操作,可以这样做:enum
,直接用as const
的对象与字面量联合的方式来模拟枚举,这样可能更灵活,也无额外编译产物。9. 利用类型参数默认值来减少繁琐的泛型调用
在编写一些大型泛型时,给类型参数设置默认值往往能减少调用者的心智负担。就像在函数里给形参设默认值一样:
10. 条件类型与“空数组”/“空对象”陷阱
在写一些条件类型时,如果传入空数组
[]
或空对象{}
,可能会出现意外的分发或推断结果。例如[]
可能被推断为never[]
或readonly []
之类。为避免踩坑,常常要加上一些显式的“兜底处理”或“中间断言”。比如:any[]
/{}
。下面再给你补充一批更偏“冷门”或“技巧性超高”的 TypeScript 玩法,部分内容在社区中被称为**“类型体操”(type gymnastics),也有些是工程中潜在会遇到的高级用法。它们往往伴随更高的认知难度和编译器负担**,在需要极致灵活度或特殊需求时会显得非常“好用”,但也要谨慎评估维护成本。
1. 使用
declare global
扩展全局类型或 Window 对象有时候我们需要在全局作用域里增补自定义的属性、函数或类型,比如往
Window
上挂载一些全局变量,或者给 Node.js 中的global
增加字段。这时可以在一个.d.ts
文件或带有declare
的文件中使用declare global { ... }
。window.myGlobalVar
或global.myGlobalFlag
。tsconfig.json
中没有排除这些声明文件(skipLibCheck
、exclude
等配置不要忽略它们)。2. 变长(Variadic)元组类型:函数管道、柯里化等场景
当一个函数需要将参数“逐级传递”给下一个函数,或做类似函数管道 / 柯里化的操作时,可能要在类型层面处理不定长度的参数元组。TypeScript 支持“变长元组类型” (
...T
),可用在泛型上做灵活接收与传递。ts-toolbelt
)来实现高阶管道 / 柯里化。3. “类型级字典合并”——多对象 / 表单配置的合并
当我们想在类型层面将多个“对象配置”合并成一个大对象(类似多源合并),可以先把对象们变成联合类型,再做Union -> Intersection,最后用键重映射来实现“同名键如何处理”的逻辑。
4. “类型级子集 / 超集”——基于联合类型的权限或角色控制
在类似权限或角色(RBAC,Role-Based Access Control)的系统中,可能需要在类型层面定义“子集”与“超集”的关系。比如在 TS 中,我们可以把角色权限表示成一个联合类型,再进行“部分选择”(子集)或“拓展”(超集)判断。
6. “类型级 BFS / DFS”:flatten 或 unflatten 嵌套对象
在一些极度复杂的数据结构处理场景,需要将嵌套对象“打平” (
flatten
),或把“路径式”键值对“还原” (unflatten
). 这可以在类型层面做,但实现起来往往非常复杂,需要递归处理键与嵌套对象。示例:简化的
Flatten
类型(递归将子对象属性拼接为foo.bar
形式):UnionToIntersection
之类的技巧把它从联合类型收敛成一个对象。7. “收窄泛型”:为同一个函数写多个“专用签名”再结合一个通用实现
TypeScript 的函数重载声明可以让我们为同一个函数写多种签名,但有时我们想在同一个泛型函数里做针对不同
T
值的专门处理,可以写“有条件分支”的类型签名。as any
,因为 TS 无法自动根据返回类型作流动分析。8. 在函数参数处做“类型安全解构”并保留整套推断
当一个函数的参数是个大型对象或深度结构,需要解构出多个字段并保留类型信息时,可以先用泛型接住整个对象,接着在内部“分解”这个泛型,从而避免写一堆繁琐的接口或 type:
({ a, b, nested: { x } }: { a: number; b: string; nested: { x: boolean } }) => ...
也行,但那样的函数就不具备泛型扩展能力。<T extends ...>
可以在外部额外传一些属性,不受强制限制,但仍保留对a
,b
,nested.x
的精确检查。10. 自定义 ESLint 规则 + TypeScript AST,让代码风格与类型信息融合检查
在更专业的工程实践中,有时不光需要类型检查,还要结合AST 检查来编写自定义的 ESLint 规则(或 TSLint 的旧时代)。可以使用官方提供的
@typescript-eslint/parser
以及@typescript-eslint/experimental-utils
去写访问器,从而在编译时拿到类型信息、在 ESLint 时拿到代码 AST,实现类似 “仅当函数返回值是 Promise 时,必须使用 async”等更高级的语法+类型混合规则。8. 用“额外类型参数”去承载中间状态,做更复杂的链式调用
当你写链式 API 时,可能不仅想保留当前的“上下文类型”,还想同时保存一些中间状态或历史记录。可以借助“额外的泛型参数”来承载这部分信息,每次调用都返回“新的泛型上下文”:
9. “Overlay” 技巧:将一个类型拆分为若干可局部覆盖的部分
在大型项目中,有时想先声明一个基础类型,然后在多个不同文件里对其某些字段进行“覆盖”,再合并回去使用。可以用交叉类型(
&
)或映射类型做“Overlay”:Partial<BaseConfig>
之类的做“增量覆盖”,配合&
合并成一个最总结构。10. 借助
typescript-is
或类似库,在运行时与编译期同步做类型校验TypeScript 的类型系统是静态的,如果还想在运行时验证一个值真的符合声明的接口,可以借助第三方库,例如 typescript-is 或 ts-json-schema-generator + ajv 等。
示例(基于 typescript-is):
1. 巧用 “至少一个属性” (
RequireAtLeastOne
)在业务中常会遇到“这个对象里至少要有一个字段是必填,其他是可选”的需求。可以写一个工具类型来强制此规则,比如一个搜索条件对象,你要求至少有一个搜索项不是空。
2. “只能且必须有一个属性” (
RequireExactlyOne
)与上面类似,但更严格:只能也必须选择其中一个键(互斥属性)。在后端或前端的接口中常见:例如“要么传 email,要么传 phone,但不能同时传或都不传”。
3. 处理 “可选属性” vs. “属性可为 undefined” 的区别
在声明接口或类型时,
?
代表该属性是可选的,而| undefined
代表它存在但值可能是undefined
。在实践中这两者会影响到类型守卫、对象结构等很多细节:4. 内置
ConstructorParameters
、InstanceType
:推断类的实例与构造函数参数在面向对象场景里,有时我们需要获取某个类的构造参数类型,或获取类的实例类型。TypeScript 内置了以下 Utility Types:
5. ReturnType + Overloaded Function 场景下的最佳实践
当函数有重载签名时,
ReturnType
只会获取到最后一个实现签名的返回类型。如果想分别获取各个重载的返回类型,需要借助其他技巧(如条件类型、类型级分发等)。一个常见的做法是:不写多重重载声明,而是写一个联合类型参数并在实现里做类型守卫,以保证
ReturnType
能正常推断具体分支。8. Type Alias + Utility:封装你的“业务语义”
在实际开发中,我们常会面对类似 “ID 类型”、“Token 类型”、“URL 类型”等,它们本质上都是 string,但含义不同,不能混用。可以用品牌化(Branding) 或最简单的type alias 来让代码更可读:
OrderId
当UserId
用,而在代码可读性上也更清晰“这是什么 ID”。下面再为你补充一批更贴近日常开发、更偏“实战”而且依旧能展现 TypeScript 灵活性与安全性的技巧与思路。这些技巧大多在写业务代码、对接接口或组织项目结构时能起到明显的便利作用,希望对你的项目有所帮助。
1. 利用
keyof typeof
创建从对象/常量自动生成的“键”联合类型有时我们想将对象的键收集起来变成一个“字面量联合类型”,以便在后续做严格校验或自动提示;可以使用
keyof typeof someObject
:'hello' | 'goodbye' | 'thankYou'
,一旦对象键有变动,类型也会自动同步。2. 结合
type
+ “标签”字段,做更灵活的“代替枚举”方案在很多场景下,与其使用
enum
,直接用常量对象 + 字面量联合的方式可能更轻便,也不会在编译后额外生成 JavaScript 产物:COLOR.RED
=> 'RED'),在编译期拥有字面量类型'RED'
|'GREEN'
|'BLUE'
。3. 使用
interface
+type
混合设计,分工明确interface
:适合描述“对象结构”,可在多个地方声明并自动合并,特别适合对外暴露的 API、配置对象、类的形状等。type
:更灵活,可以写联合、交叉、条件类型等高级用法,也能定义函数类型或复杂的泛型类型工具。示例:
type
发挥灵活性。4. 在大型项目里使用
paths
和baseUrl
优化导入TypeScript 的
tsconfig.json
中,可以设置"baseUrl"
和"paths"
,让你在导入文件时使用更简洁的别名,而不是到处写相对路径../../...
。例如:import { formatDate } from '../../utils/date'
。5. 使用 “内置”声明文件里已有的
JSX.IntrinsicElements
做自定义组件属性推断当写 React 或 Vue3(JSX 方案)时,想在自定义组件上也有类似原生标签
div
一样的属性推断,可以在类型层面声明或扩充JSX.IntrinsicElements
。比如自定义<MyButton>
:onClick
、disabled
等按钮属性。vue-tsx-types
等辅助库。8. 在单元测试中使用“类型断言 + 类型谓词”简化测试代码
有时我们写单元测试(特别是对库/工具函数的测试)需要直接断言类型是否正确,可以通过类型断言 + dummy 变量 + 类型谓词来巧妙验证。例如:
expectType<T>(value: T)
的作用仅在编译期,在运行时什么也不做,但能让我们知道result
是否真为number
。testType
工具函数,用在测试文件里专门断言类型推断正确,不用手动在代码中写as number
。9. 使用 重载 + Rest 参数,兼容多种调用方式(可选回调、Promise 等)
在一些库函数中,想既支持“回调风格”,又支持“Promise 风格”,可以写函数重载,让调用者在 TS 层面获得正确的参数/返回值提示:
void
还是Promise<string>
。好的,这里再补充一波**“干货”,同样围绕在日常开发和团队协作中会遇到的真实场景**,包括更少人提及却很实用的细节、编译配置、调试技巧,以及一些常见的“坑”与对应的解决方案。希望对你有所启发!
1. 巧用
@ts-expect-error
和@ts-ignore
的差异在需要忽略某些 TS 报错时,我们常见做法是用
// @ts-ignore
。但 TypeScript 还提供了更安全的// @ts-expect-error
注释:@ts-ignore
:无条件忽略下行所有 TS 报错,不会在编译时做任何检查。@ts-expect-error
:告诉编译器“我预期这里会有一个错误”,如果编译器发现没有错误,就会报“多余的 expect-error”警告,反过来也可以帮我们发现“修复后忘了删除的忽略注释”。@ts-expect-error
,这样如果不再需要忽略时能被编译器提示;不要随意滥用@ts-ignore
让类型系统失去意义。2. 给第三方库类型打“补丁”时,避免冲突与版本不兼容
当你需要覆盖或补充第三方库的类型定义时(尤其是 DefinitelyTyped 上的
@types/xxx
),最好在本地声明文件里用以下方式区分开来,避免将来库升级冲突或命名重叠:types/patches/xxx-augment.d.ts
中写declare module 'xxx' {...}
。tsconfig.json
的"include"
或"files"
中显式包含这些声明文件,确保它们被读取。这样做能让你在不 fork 第三方类型仓库的前提下,灵活地修正或扩展它的类型,而当库版本升级时,也比较好排查和维护。
4. 注意
strictNullChecks
与strict
选项的开启在
tsconfig.json
里,TypeScript 的"strict": true
(或至少"strictNullChecks": true
)可以帮助你杜绝很多潜在的null
/undefined
错误,让 TS 的类型推断更严格、更准确:"strict"
,如果是老项目,可以分步骤迁移。5. 充分运用
noUncheckedIndexedAccess
,避免数组/字典越界TypeScript 4.1+ 新增了
noUncheckedIndexedAccess
编译选项。一旦开启,当你访问数组或对象索引时,类型会带上| undefined
,提示“可能越界或无此键”:undefined
判断,需要与业务逻辑匹配。若团队能接受,开启后能显著提升安全性。6. 使用
type-only import/export
:让编译产物更干净在 TypeScript 3.8+ 中,出现了
import type
和export type
这两个新语法,它们仅在编译期保留类型信息,而不会在编译后的 JavaScript 产生额外的import
/export
代码。示例:import type
可减少编译后不必要的 import 语句、提升清晰度。7. 避免“命名冲突”与“默认导出 + 命名导出”混乱
在实践中,由于 TypeScript 对默认导出(
export default
)与命名导出(export { ... }
)都支持,常会出现命名冲突、引用混乱等问题。常见建议:user-service.ts
中export default class UserService
.8. 用
esModuleInterop
、allowSyntheticDefaultImports
处理 CommonJS 模块兼容有时需要引入采用 CommonJS 规范的库(例如老版本的
module.exports = ...
),在 TypeScript 中若想直接import x from 'xxx'
,需要在tsconfig.json
中设置以下选项,确保 TS 能正确编译、保留默认导出语义:esModuleInterop
会帮你自动为 CommonJS 导出添加默认导出兼容;allowSyntheticDefaultImports
则允许你使用import defaultExport from 'module-name'
即使其实它没有真的默认导出。如果不配置,在引入 CommonJS 库时需要用
import * as x from 'xxx'
或const x = require('xxx')
来避免编译错误。好的,再为你补充一批**“高级而又实用”的 TypeScript 技巧/知识点,重点聚焦在较新版本**(4.x 到 5.x)的改进或常被忽略却能大幅提升开发体验的点,帮助你与时俱进地发挥 TypeScript 的威力。
2. TS 5.0 “稳定版装饰器(Decorators)”与元数据
TypeScript 5.0 将装饰器(Decorators)升级为稳定语法(之前是实验特性),并借鉴了“ECMAScript decorators”提案的一些变更。它支持对类、方法、访问器、属性、参数进行声明式的“扩展”。在TS 5.0中:
experimentalDecorators
语法略有区别,需要你查看新的 Decorators 规范。3. “模块后缀” (
moduleSuffixes
) 配置,兼容.js
/.mts
/.cts
等从 TS 4.7 起,Node.js ES Module 环境中需要区分后缀(如
.js
、.mjs
、.cjs
等)。tsconfig.json
提供了"moduleSuffixes"
选项,可以用来兼容不同后缀的导入方式。例如:.ts
,引入却是./foo.js
时,编译器也能正确解析到./foo.ts
。4.
verbatimModuleSyntax
(TS 5.2+)保留导入导出语句原样TypeScript 5.2 新增了
verbatimModuleSyntax
试验性配置,让编译器在转译时保留原始的 import/export 路径、语法,不做额外的拓展或后缀处理(前提是你使用的是target: "ESNext"
之类)。好处:5. 改善 watch 模式:
incremental
+watch
+skipLibCheck
对于大中型项目,开启以下配置可以显著加快编译和增量构建速度:
incremental
:让 TS 生成.tsbuildinfo
文件,后续只重新编译改动过的部分。skipLibCheck
:跳过对node_modules
中声明文件的严格检查,一般不会影响你项目的类型安全,却能节省大量编译时间。watchOptions
:在大项目里,用事件监控 (useFsEvents
) 而非轮询,可以减少 CPU 占用。8. 泛型工具:基于
extends keyof any
/PropertyKey
做灵活键类型当我们需要声明“某个键必须是字符串/数字/符号”时,可以用 TS 的
keyof any
或PropertyKey
(二者同义)来表示可当键的联合类型:10. 可选属性的“精确性”:
exactOptionalPropertyTypes
(TS 4.4+)exactOptionalPropertyTypes
可以让 TS 更精准地区分“确实存在但为undefined
”与“属性根本没定义”。开启后,?
属性就真的表示“此属性可能不存在”,而不是“存在且可以是 undefined”。示例:| undefined
。好的,再补充一批更“高阶”或“深度”一点的 TypeScript 技巧和思路,涵盖一些编译性能优化、类型推断边界、工具链集成和少见却实用的场景,希望对你有帮助。
5. 让“
never
”做穷尽性校验还能检测联邦数据流遗漏我们常在
switch
语句或reducers里,用“never
变量”来检查是否处理完了所有联合类型成员:Action
新增了'toggle'
而忘了在switch
中处理,编译器就会在exhaustiveCheck
这一行报错,提醒开发者补齐分支。6. 同时使用多种
npm scripts
+tsc
+ Bundler 时的工程结构在前后端或多包结构(monorepo)项目里,有时需要多套
tsconfig
配置:例如:
然后在
package.json
的scripts
中,根据需要不同tsconfig
执行:tsconfig
里过度折衷。7. 巧妙使用 “类型层面的依赖注入” 或 “层级推断”
在一些依赖注入(DI)或插件式场景中,我们可以用泛型的方式让每个插件在类型层面“注入”新的特性。例如:
8. 用 “参数属性” 写法 简化类构造
TypeScript 提供的“参数属性” (Parameter Properties) 语法,可以让我们在构造函数参数中直接声明
public
/private
/readonly
等,避免冗长的重复声明:9. “绝对路径” vs “相对路径” vs “包内导入” 三选一的团队规范
在团队里,引用其他模块常见方式有:
import ../utils/tool
baseUrl
):import utils from 'src/utils'
"paths"
或 monorepo 的package.json
name):import { something } from '@myorg/utils'
建议:选定一种适合团队和项目结构的方式,并在
tsconfig.json
里配置好"baseUrl"
、"paths"
.package.json
里设"name": "@myorg/packageA"
, 并将其链接到根下做包内导入。"baseUrl": "src"
+"paths"
来避免深层相对路径嵌套。10. 处理“第三方类型”不完美时的“前置断言”/“后置断言**”
有时第三方库的类型定义可能不够精确,比如声明返回值过于宽泛 (
any
/unknown
等),我们可以在调用处先做断言或封装成一个更精准的函数。例如:getMyData()
,避免在任何地方都要写as { ... }
.The text was updated successfully, but these errors were encountered: