@skyroc/utils/type —— 零运行时的 TypeScript 工具类型:深度路径推导、递归可选化、联合类型变换与函数类型提取
@skyroc/utils/type 是一组零运行时的 TypeScript 工具类型,编译后不产生任何代码。它主要服务于 @skyroc/form 这类需要在编译期推导嵌套字段路径的场景。
import type { LeafPaths, PathValue, DeepPartial } from '@skyroc/utils/type';这些类型原先住在独立的 @skyroc/type-utils 包里。该包唯一的两个消费者(@skyroc/form 与 @skyroc/utils
自身)都已经依赖 @skyroc/utils,独立发包只是在重复付版本、README、构建与 发布的成本,因此从 @skyroc/utils@4.0.0
起并入本子入口。迁移方式见 迁移。
| 导入路径 | 内容 | 需要 DOM lib |
|---|---|---|
@skyroc/utils/type | 本页的全部类型 | 否 |
@skyroc/utils/web | FieldElement、CustomElement | 是 |
这个拆分是必要的:React Native 侧会导入 ./type,那里只要出现一处 DOM 全局类型,无 DOM lib 的环境就编译不过。该边界和运行时代码走同一套 tsconfig 强制——./type 所属的 TS 项目 lib 只有 ESNext。
| 类型 | 说明 |
|---|---|
Primitive | 真正的 JS 原始类型:string | number | boolean | bigint | symbol | null | undefined |
Atomic | 递归终止集合:Primitive 加上 Date、RegExp、Error、Map、Set、WeakMap、WeakSet 以及任意函数 |
IsAny<T> | 只有 any 为 true |
IsTuple<T> | 定长元组为 true,变长数组为 false |
Primitive 刻意保持狭义。想表达「不要再往里递归」的,用 Atomic——本子入口所有深度工具都停在它这里。
DeepPartial<T>递归可选化。停在 Atomic,保留元组形状,保留数组可变性;并且——不同于朴素的同态映射——不会把数组元素污染成 T | undefined。
type Cfg = { when: Date; list: { id: number }[]; pair: [{ a: number }, string] };
type T = DeepPartial<Cfg>;
// { when?: Date; list?: { id?: number }[]; pair?: [{ a?: number }, string] }ShallowPartial<T>同样的语义,但只作用一层。Atomic 类型与数组原样返回。
Prettify / MergeUnion / UnionToIntersection / Wraptype A = Prettify<{ a: number } & { b: string }>; // { a: number; b: string }
type B = UnionToIntersection<{ a: 1 } | { b: 2 }>; // { a: 1 } & { b: 2 }
type C = MergeUnion<{ a: 1 } | { b: 2 }>; // { a: 1; b: 2 }
type D = Wrap<'a' | 'b', number>; // { a: number; b: number }MergeUnion 只展平顶层,嵌套层的同名字段仍保留为交叉类型。
所有路径工具共享同一套规则:
Atomic,因此 Date 不会贡献出 when.getTime 这种路径`${number}`,字面量 number 作为「任意下标」的通配写法undefinedDepth 参数约束(默认 6),自引用类型可以正常编译LeafPaths / AllPathsLeafPaths 只给叶子路径,AllPaths 额外包含中间的对象与数组本身。
type FormValues = { age: number; info: { city: string }; list: { id: number }[] };
type L = LeafPaths<FormValues>;
// 'age' | 'info.city' | `list.${number}.id`
type A = AllPaths<FormValues>;
// 上面三条,再加 'info' | 'list' | `list.${number}`| 类型 | 说明 |
|---|---|
AllPathsKeys<T> / AllPathsShape<T> | 路径键联合 / 路径到值类型的映射 |
PathValue<T, P> | 取出路径 P 上的值类型 |
PathToType<T, P> / PathToDeepType<T, P> | 由单条路径反推对象结构(浅 / 深) |
ShapeFromPaths<T, Ps> | 由一组路径反推对象结构 |
ArrayKeys<T> / ArrayElementValue<T, K> | 数组字段键 / 其元素类型 |
Join<P, K> | 路径片段拼接 |
所有路径工具末尾都有一个 Depth 参数,默认 6:
interface TreeNode {
children: TreeNode[];
name: string;
}
type P = AllPathsKeys<TreeNode>; // 正常编译,展开到 6 层
type Q = AllPaths<TreeNode, '', 3>; // 更浅,编译更快没有这个上限时,自引用类型会直接触发 TS2589: Type instantiation is excessively deep and possibly infinite。表单类型很大导致编译变慢时,可以把深度调小。
| 类型 | 说明 |
|---|---|
Fn<A, R> | 通用函数签名 |
Noop | () => void |
FunctionKeys<T> | T 中值为函数的键 |
FunctionUnion<T> | T 中所有函数值的联合 |
OnlyFunctions<T> | 只保留函数字段的对象类型 |
| 类型 | 说明 |
|---|---|
MaybeArray<T> | T | T[]。原属 @skyroc/ui-types,它和 DeepPartial 是同类,一并归到这里 |
依赖 DOM lib,因此不在本子入口,而在 @skyroc/utils/web:
import type { CustomElement, FieldElement } from '@skyroc/utils/web';@skyroc/types 的区别| 包 | 内容 | 形态 |
|---|---|---|
@skyroc/types | 业务 / 全局类型(Api、Router、I18n…) | declare global 全局注入 |
@skyroc/utils/type | 通用工具类型(路径、变换、函数) | 普通 export,按需 import |
@skyroc/type-utils 迁移类型本身的名字、签名、语义都没有变,只换导入路径:
| 旧 | 新 |
|---|---|
import type { LeafPaths } from '@skyroc/type-utils' | from '@skyroc/utils/type' |
import type { FieldElement } from '@skyroc/type-utils/web' | from '@skyroc/utils/web' |
@skyroc/type-utils 已废弃,不再发布新版本。
Last updated on