@skyroc/utils

类型工具

@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/webFieldElementCustomElement

这个拆分是必要的:React Native 侧会导入 ./type,那里只要出现一处 DOM 全局类型,无 DOM lib 的环境就编译不过。该边界和运行时代码走同一套 tsconfig 强制——./type 所属的 TS 项目 lib 只有 ESNext

原始类型与判定

类型说明
Primitive真正的 JS 原始类型:string | number | boolean | bigint | symbol | null | undefined
Atomic递归终止集合:Primitive 加上 DateRegExpErrorMapSetWeakMapWeakSet 以及任意函数
IsAny<T>只有 anytrue
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 / Wrap

type 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 作为「任意下标」的通配写法
  • 可选字段只贡献键名,不会带出 undefined
  • 递归受 Depth 参数约束(默认 6),自引用类型可以正常编译

LeafPaths / AllPaths

LeafPaths 只给叶子路径,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 是同类,一并归到这里

Web 表单元素类型

依赖 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