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

TypeScript Nested keys泛型约束报错:ShortHandKeyMapper定义问题

解决嵌套路径拆分的泛型类型约束问题

问题描述

需要创建一个泛型类型ShortHandKeyMapper,包含两个属性:

  • control:嵌套路径的前缀部分
  • value:该前缀对应的剩余路径部分

原实现出现“不满足约束”的错误,且无法正确拦截control与value的非法组合(比如错误允许control: 'level1.level2.level3'搭配value: 'level2')。

原代码问题分析

原ShortHandKeyMapper的泛型参数设计导致control和value的类型未关联:

type ShortHandKeyMapper<F extends object, Base extends RecursiveKeyOf<F> = RecursiveKeyOf<F>> = {
    control: Base                              
    value: SubRecursiveKeys<RecursiveKeyOf<F>, Base>
}

当未显式指定Base时,它是RecursiveKeyOf<F>的联合类型,此时SubRecursiveKeys会尝试对整个联合类型计算,最终得到never类型,导致value与control的类型不匹配,触发约束错误。同时这种写法无法保证control的具体值和value的子路径一一对应。

解决方案

将ShortHandKeyMapper设计为联合类型,让每个control值对应唯一合法的value类型:

export type RecursiveKeyOf<TObj extends object> = {
    [TKey in keyof TObj & (string | number)]: TObj[TKey] extends any[]
    ? `${TKey}`
    : TObj[TKey] extends object
    ? `${TKey}` | `${TKey}.${RecursiveKeyOf<TObj[TKey]>}`
    : `${TKey}`;
}[keyof TObj & (string | number)];

type SubRecursiveKeys<RK extends string, PFX extends RK> = 
  RK extends `${PFX}.${infer SubKey}` ? SubKey : never;

// 修正后的 ShortHandKeyMapper
type ShortHandKeyMapper<F extends object> = {
  [K in RecursiveKeyOf<F>]: {
    control: K;
    value: SubRecursiveKeys<RecursiveKeyOf<F>, K>;
  };
}[RecursiveKeyOf<F>];

// 测试对象
const a = {
    level1: {
        level2: {
            level3: {
                level4: {
                    level5: 'test'
                }
            }
        }
    }
}

验证效果

  • 非法组合会被TypeScript拦截:
const denyMapper: ShortHandKeyMapper<typeof a> = {
    control: 'level1.level2.level3',
    value: 'level2' // 错误:类型'"level2"'不能赋值给类型'"level4" | "level4.level5"'
}
  • 合法组合正常通过:
const okMapper: ShortHandKeyMapper<typeof a> = {
    control: 'level1.level2.level3',
    value: 'level4' // 正常通过
}

const okMapper2: ShortHandKeyMapper<typeof a> = {
    control: 'level1.level2.level3',
    value: 'level4.level5' // 正常通过
}

可选优化:支持叶子节点的空值

如果希望前缀是叶子路径时,value可以省略,可将value设为可选属性:

type ShortHandKeyMapper<F extends object> = {
  [K in RecursiveKeyOf<F>]: {
    control: K;
    value?: SubRecursiveKeys<RecursiveKeyOf<F>, K>;
  };
}[RecursiveKeyOf<F>];

// 叶子路径的合法写法
const leafMapper: ShortHandKeyMapper<typeof a> = {
    control: 'level1.level2.level3.level4.level5'
    // value 可省略,因为无后续路径
}

内容的提问来源于stack exchange,提问作者Eugen Halca

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 05:25:54