- 开发工具
【免费下载链接】ts-toolbelt
👷 TypeScript's largest type utility library
本篇基于 CHANGELOG.md 中记录的 9.6.0(2021-03-10)发布条目展开,逐条对照 sources 下的类型源码与 tests 下的类型测试,讲解该版本在路径自动补全(AutoPath)、参数窄化(Narrow)、函数组合(Compose)、路径操作(O.Paths / O.P)等模块中引入的能力与修复,帮助你在升级到 9.6.0 时准确判断这些改动对现有类型代码的影响,并学会利用新增的自定义分隔符参数编写更安全的类型路径。
ts-toolbelt 是 TypeScript 的类型工具库(当前仓库版本即 9.6.0,见 package.json),其 CHANGELOG 由 standard-version 依据 Conventional Commits 提交规范自动生成。9.6.0 的核心主线是"让路径类类型更准确、让函数类型更可控":为F.AutoPath增加了自定义分隔符参数,修复了F.Narrow在数组推断、方差(variance)与高阶函数场景下的缺陷,并允许柯里化函数参与F.Compose组合。
版本概览与发布机制
| 条目 | 内容 |
|---|---|
| 版本号 | 9.6.0 |
| 发布日期 | 2021-03-10 |
| 变更来源 | CHANGELOG.md,由 standard-version 生成 |
| 提交规范 | .commitlintrc.json扩展@commitlint/config-conventional,即常规提交约定 |
| 变更分类 | Features(新特性)、Bug Fixes(缺陷修复)、Others(重构与版本号维护) |
从 package.json 的 devDependencies 可见standard-version@^9.1.0是版本管理工具,实际发布流程封装在 scripts/release.sh 中:
- 先执行
npm run test确保全部类型测试通过; - 用
node -p判断package.json中版本号的 patch 位是否为0,以此决定是否属于正式发布; - 非 master 分支执行
npx standard-version --skip.tag(只生成 changelog 不打 tag); - master 分支且非正式发布时同样
--skip.tag;正式发布时执行完整npx standard-version并git push origin $BRANCH --follow-tags。
因此 CHANGELOG 中大量 "release: 9.5.x" 的 Others 条目,是这一自动发布流程在 9.5.2~9.6.0 之间的中间产物,它们在 9.6.0 发布时被合并进同一份变更记录。
新特性:F.AutoPath 支持自定义分隔符
本次唯一标注为 Features 的变更,是为AutoPath增加路径分隔符参数。AutoPath位于 sources/Function/AutoPath.ts,其公开签名是:
export type AutoPath<O extends any, P extends string, D extends string = '.'> = _AutoPath<O, P, D>;三个参数的语义分别为:
O:待探测路径的对象类型;P:当前已输入的路径字符串;D(可选,默认'.'):路径的分隔符。
在 9.6.0 之前,路径只能以.分隔;现在可以传入任意字符串作为分隔符。实现上,_AutoPath通过Split<P, D>将已输入路径按分隔符拆成索引列表,再配合内部的MetaPath(把对象递归映射为"路径字符串联合")与ExecPath/NextPath(在元路径上按索引取值并过滤出下一步可达路径)完成自动补全。这一机制在 tests/Function.ts 中有完整的验证用例,例如:
check<F.AutoPath<O_AUTOPATH, 'b/b/0/a', '/'>, 'b/b/0/a' | 'b/b/0/a/a', Test.Pass>(),O_AUTOPATH中b.b是O_AUTOPATH[]数组,测试确认用/作为分隔符同样能命中数组下标0并给出后续可补全路径;而check<..., 'b.b.0.a' | 'b.b.x.a.a', Test.Fail>()则验证错误路径会被排除。配套的典型用法(来自源码注释)是声明一个基于AutoPath的取值函数:
declare function get<O extends object, P extends string>( object: O, path: AutoPath<O, P> ): Path<O, Split<P, '.'>> // 合法:friends 是数组,40 为下标 const friendName = get(user, 'friends.40.name') // 报错:names 并非有效字段 const friendNames = get(user, 'friends.40.names')F.AutoPath 系列修复:分发、原始类型属性与空路径
9.6.0 同时修复了f.autopath的三个问题,三者都聚焦于补全候选的精确性:
- distribute(分发):确保
AutoPath在对象为联合类型时按联合成员分发处理,避免只取联合中的一个分支导致补全缺失; - do not show primitive props(不再展示原始类型的属性):此前
AutoPath可能把string/number等原始类型的内建属性(如length、toString)也作为补全候选,修复后只枚举用户数据结构自身的路径; - no primitive paths(排除原始类型路径):与上一条配合,避免
Primitive内部路径污染补全结果。
这与MetaPath的实现相互印证:MetaPath递归展开[K in keyof O]时会把每个子属性映射成Join<[...P, KeyToIndex<K, SP>], D>与更深的MetaPath的联合,而NextPath通过Exclude<OP, string>剔除直接的字符串路径后再Select<..., string>取得下一级路径,从而保证只提示结构内可达的字段。若你从旧版本升级后补全提示变少,很可能正是这些"本不该出现"的原始类型路径被正确移除了。
F.Narrow 系列修复:方差、数组推断与高阶函数
9.6.0 对F.Narrow(见 sources/Function/Narrow.ts)做了密集修复,集中解决"泛型参数被拓宽(widening)"的经典痛点:
- variance(方差):包括
fix variance and array inference、variance与revert variance fixes,前两者修正Narrow在函数参数协变/逆变场景下的行为,后者说明部分方案被回退,属于实现层面的试错与收敛; - higher order function support(高阶函数支持):
Narrow现在能正确处理作为参数传入的函数类型; - preserve fns / preserve functions(保留函数):窄化过程中函数类型的成员不再被展开破坏,
preserve fns and better display还改进了类型在编辑器中的展示效果。
Narrow的实现核心是NarrowRaw:对[]直接返回、对Narrowable(string | number | bigint | boolean,见 sources/Function/_Internal.ts)原样保留,其余对象则递归映射,且遇到Function成员时保留原类型而非继续深入。测试 tests/Function.ts 验证:
declare function narrowList<A extends any[]>(x: F.Narrow<A>): A; const test05 = narrowList(['e', 2, true, {f: ['g', ['h']]}]) // typeof test05 === ['e', 2, true, {f: ['g', ['h']]}],字面量被完整保留也就是说,F.Narrow能让['e', 2, true]这类实参在推断时保持为精确的字面量元组,而不是拓宽成(string | number | boolean)[],这是实现"不可变配置对象字面量类型"的常用手段。
函数组合:F.Compose 允许柯里化函数参与
变更fn: allow for curried in compose意味着F.Compose组合的函数可以是普通函数或柯里化(Curry)函数。入口类型定义在 sources/Function/Compose.ts:
export type Compose<mode extends Mode = 'sync', input extends Input = 'multi'> = IntersectOf<{ 'sync': { 'multi': ..., 'list': ... }, 'async': { ... } }[mode][input]>其中Mode = 'sync' | 'async'、Input = 'multi' | 'list'(见 sources/Function/_Internal.ts)。每个具体实现(如 sources/Function/Compose/List/Sync.ts)的每个重载都同时接受Function<...>与Curry<Function<...>>,例如:
(fns: [Function<[R0], R1> | Curry<Function<[R0], R1>>, Function<P, R0> | Curry<Function<P, R0>>,]): Function<P, R1>测试 tests/Function.ts 展示了实际效果——柯里化函数curry((a1: number, d2: number) => \${a1 + d2}`)与普通函数一起被composeSync接受并正确推导出最终签名;异步版本 [sources/Function/Compose/List/Async.ts](https://link.gitcode.com/i/f6dc613b1a2fcb24537f4a4411d5893c) 则在每层用Await解开Promise后再传入下一层,最终返回Promise<Await >,从而让F.Compose<'async'>的链式推导与await` 语义完全对齐。
O.Paths 深入数组:路径枚举覆盖数组元素
修复o.paths: dive within arrays and deeper使O.Paths能够下探到数组元素内部的路径。核心实现见 sources/Object/Paths.ts:
type _Paths<O, P extends List = []> = UnionOf<{ [K in keyof O]: O[K] extends BuiltIn | Primitive ? NonNullableFlat<[...P, K?]> : [Keys<O[K]>] extends [never] ? NonNullableFlat<[...P, K?]> : 12 extends Length<P> ? NonNullableFlat<[...P, K?]> : // 实例化深度上限 _Paths<O[K], [...P, K?]> }>递归时若子属性是BuiltIn/Primitive或没有可枚举 key 则停止下探,否则继续递归并把当前 key 追加进路径。值得注意的是12 extends Length<P>——这是源码中为控制类型实例化深度而设置的路径长度上限(12 层),避免深度嵌套类型引发实例化过深错误。测试 tests/Object.ts 确认{prop: {a: 1}[]}可枚举出['prop', number, 'a']形式的路径,即数组元素属性也被纳入路径联合;CHANGELOG 中同期的string: instantiation limiters(字符串工具加入实例化限制器)也是同类"深度/性能防护"思路的体现,例如 sources/String/Split.ts 通过__Split递归加Cast完成分隔拆分。
路径操作重实现:O.P 模块与更灵活的 List 键
9.6.0 的 Others 部分还包含两次结构性改动:
- o.p: re-implement(O.P 模块重实现):
O.P(Path)命名空间下的操作被整体重写。以 sources/Object/P/Merge.ts 为例,Merge<O, Path, O1, depth = 'flat'>通过MergeAt沿Path递归定位,路径末端用OMerge(支持'flat' | 'deep'深度模式)合并字段;sources/Object/P/Update.ts 的UpdateAt则在路径末端区分List与object,分别调用 sources/List/Update.ts 与OUpdate完成更新。重实现后的行为由 tests/Object.ts 验证,包括对路径段为联合('a' | 'b')时的分发处理,以及O.P.Update对深层可变路径的支持。 - list: more flexible keys(List 键更灵活):List 相关类型对键的约束放宽,例如 sources/List/Update.ts 中
Update<L, K, A>将K同时归一化为`${K & number}` | K,从而同时兼容数字索引与字符串键。
升级到 9.6.0 的实操要点
- 分隔符参数:使用
F.AutoPath<O, P, D>时可按需传入D(如'/'),默认仍为'.',旧代码无需改动即可升级。 - 补全行为变化:若依赖
F.AutoPath的编辑器提示,升级后原始类型内建属性将不再出现在候选中,这是有意为之的修复,请勿视为回归。 - Narrow 更可靠:涉及数组字面量、高阶函数参数的
F.Narrow场景,升级后可获得更精确的字面量推断,同时可关注编辑器中类型展示的改善。 - Compose 组合柯里化函数:
F.Compose同步/异步、multi/list 四种组合均接受Curry包裹的函数,可放心混用。 - 深度上限提示:
O.Paths存在 12 层深度上限(见 sources/Object/Paths.ts),设计超深数据结构时应考虑路径枚举的边界。
所有上述行为均在 tests/Function.ts 与 tests/Object.ts 中有对应check断言,升级后运行npm run test(内部执行 scripts/test/all.sh)即可在本仓库验证这些类型的正确性。
- 开发工具
【免费下载链接】ts-toolbelt
👷 TypeScript's largest type utility library
相关推荐
Less Framework响应式设计原理:从HTML结构到CSS网格的实现方法
Less Framework响应式设计原理:从HTML结构到CSS网格的实现方法 Less Framework是一款轻量级的响应式CSS网格系统,专为构建自适应
前端Klee One字体:优雅手写风格的终极开源解决方案
Klee One字体:优雅手写风格的终极开源解决方案 在数字设计的世界里,寻找一款既优雅又实用的手写字体往往令人头疼。今天,我要向大家强力推荐一款来自Fontw
Obsidian终极绘图指南:用Drawio插件打造可视化知识库
Obsidian终极绘图指南:用Drawio插件打造可视化知识库 还在为Obsidian笔记缺少可视化功能而烦恼吗?想要在知识管理中添加专业图表却不想频繁切换软
知识管理
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考