You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在TypeScript中实现仅类型上下文可见的透明可推导嵌套类型

你需要实现的需求对应的标准技术术语是幽灵类型(Phantom Type),也叫类型级元数据,核心就是在类型层面存储关联信息,不会影响实际的运行时结构和智能提示。下面给出两个可直接落地的优化方案:

方案1:私有Unique Symbol存储类型元数据(改动最小)

TypeScript的智能提示默认不会展示unique symbol类型的键属性,只要你不导出这个symbol,外部代码完全感知不到它的存在,也不会出现在补全列表里,和你现有代码的适配成本最低。

实现代码:

/** 私有Symbol,仅用于类型层面存储结果引用,不导出的话外部完全无法访问 */
declare const __ActionResultType: unique symbol;

/**
 * Represents a result of an action
 */
type ActionResult<
  ActionName extends string = string,
  ResultProps = {}
> = {
  type: ActionName,
  result: true,
} & ResultProps;

/**
 * Represents an Action, but also contains within it the type of it's result
 */
type Action<
  ActionName extends string = string,
  Props = {},
  ResultProps = {}
> = {
  type: ActionName,
  result?: undefined,
  // 用私有Symbol作为键,不会出现在智能提示里
  [__ActionResultType]?: ActionResult<ActionName, ResultProps>,
} & Props;

/**
 * Type guard for a specific action
 */
function isAction<A extends Action>(actionName: A['type'], action: Record<string, unknown>): action is A {
  return action.type === actionName && action.result === undefined;
}

/**
 * Derive the Result type from an Action type
 */
type ResultFromAction<
  A extends Action
> = NonNullable<A[typeof __ActionResultType]>;

/**
 * Type guard for a specific action result (by Action)
 */
function isActionResult<A extends Action>(actionName: A['type'], result: Record<string, unknown>): result is ResultFromAction<A> {
  return result.type === actionName && result.result;
}

优势:

  • 完全兼容你现有的代码逻辑,仅需修改Action的属性键
  • 智能提示不会出现任何额外属性,外部代码无法访问该私有Symbol
  • 不需要额外的注册步骤,使用方式和你之前完全一致

方案2:全局类型映射表(完全无额外属性)

如果你希望Action类型上完全不存在任何额外的属性(哪怕是Symbol键),可以用类型映射表+声明合并的方案,把所有关联关系存在全局的类型接口里,结构100%干净。

实现代码:

/**
 * Represents a result of an action
 */
type ActionResult<
  ActionName extends string = string,
  ResultProps = {}
> = {
  type: ActionName,
  result: true,
} & ResultProps;

/** 全局类型映射表,用来存储Action名和对应结果属性的映射关系 */
interface ActionResultRegistry {}

/**
 * Represents an Action
 */
type Action<
  ActionName extends keyof ActionResultRegistry,
  Props = {}
> = {
  type: ActionName,
  result?: undefined,
} & Props;

/**
 * Type guard for a specific action
 */
function isAction<A extends Action<keyof ActionResultRegistry>>(actionName: A['type'], action: Record<string, unknown>): action is A {
  return action.type === actionName && action.result === undefined;
}

/**
 * Derive the Result type from an Action type
 */
type ResultFromAction<
  A extends Action<keyof ActionResultRegistry>
> = ActionResult<A['type'], ActionResultRegistry[A['type']]>;

/**
 * Type guard for a specific action result (by Action)
 */
function isActionResult<A extends Action<keyof ActionResultRegistry>>(actionName: A['type'], result: Record<string, unknown>): result is ResultFromAction<A> {
  return result.type === actionName && result.result;
}

使用方式:

每次定义新的Action之前,先扩展全局的映射表即可:

// 先注册所有Action的结果类型
interface ActionResultRegistry {
  navigate: { success: boolean },
  incrementCounter: { newValue: number }
}

// 再定义Action类型
type NavigateAction = Action<'navigate', { path: string }>;
type IncrementCounterAction = Action<'incrementCounter', { valueToAdd: number }>;

优势:

  • Action类型完全没有任何多余属性,结构干净
  • 所有Action的结果类型统一管理,便于维护

内容的提问来源于stack exchange,提问作者Frank Weindel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 22:30:03