【问题标题】:Retrieve subset of object where string values start with given string检索字符串值以给定字符串开头的对象子集
【发布时间】:2022-01-07 01:47:18
【问题描述】:

给定一个对象

const MyObject = {
    a: 'a',
    b1: 'b1',
    b2: 'b2',
} as const;

我想为一个只包含以字母"b" 开头的字符串值的对象创建一个类型。如何通过字符串值的内容过滤对象类型?

这是我想用typeof MyObject构造的类型:

type OnlyBs = {
    b1: "b1";
    b2: "b2";
}

为了获得所需的解决方案,我使用了template literal typesinfer 关键字:

type OnlyBs = {
    [K in keyof typeof MyObject]: typeof MyObject[K] extends `b${infer Rest}` ? `b${Rest}` : never;
};

然而,这给了我类型

type OnlyBs = {
    readonly a: never;
    readonly b1: "b1";
    readonly b2: "b2";
}

这接近我想要的,但不是所需的类型。因此,在这里将它与对象 MyObject2 一起使用会给我类型错误:

const MyObject2: OnlyBs = {
    b1: 'b1',
    b2: 'b2',
};

“类型 '{ b1: "b1"; b2: "b2"; }' 中缺少属性 'a' 但在类型 'OnlyBs' 中是必需的。(2741)"

问题是 TypeScript 要求我添加 a 属性,尽管它的类型是 never。我想不出另一种方法来实现我的目标。我觉得我已经接近了,但我无法从生成的对象类型中删除整个属性(包括它的键)。

顺便说一句:如果这样做更容易,限制对象键(而不是属性值)的解决方案也适用于我,因为在这个特定示例中,属性值等于属性键。

TypeScript Playground 我的代码示例

【问题讨论】:

  • 尝试在 OnlyBs 中返回 K 而不是 b${Rest}。这样,OnlyBs 返回允许的键。然后只需使用 Pick 即可获得所需的道具

标签: typescript mapped-types


【解决方案1】:

尝试在OnlyBs 中返回K ,而不是b${Rest}。这样,OnlyBs 返回允许的键。然后只需使用 Pick 即可获得所需的道具。

考虑这个例子:

const MyObject = {
    a: 'a',
    b1: 'b1',
    b2: 'b2',
} as const;

type AllowedKeys<Obj> = {
    [K in keyof Obj]: Obj[K] extends `b${infer _}` ? K : never; // <-- use K instead of `b{infer Rest}`
}[keyof Obj]; // use extra keyof Obj

{
    // "b1" | "b2" <-- allowed keys
    type _ = AllowedKeys<typeof MyObject>

}

type OnlyBs<Obj> = Pick<Obj, AllowedKeys<Obj>>

{
    // readonly b1: "b1";
    // readonly b2: "b2";
    type _ = OnlyBs<typeof MyObject>
}

const MyObject1: OnlyBs<typeof MyObject> = {
    b1: 'b1',
    b2: 'b2',
}; // ok

const MyObject2: OnlyBs<typeof MyObject> = {
    a: null as any, // expected error
    b1: 'b1',
    b2: 'b2',
};

Playground

如果您想使用一种实用程序类型,请考虑以下几点:

type AllowedKeys<Obj> = Pick<Obj, {
    [K in keyof Obj]: Obj[K] extends `b${infer _}` ? K : never;
}[keyof Obj]>; 

为什么映射类型 AllowedKeys 末尾的 [keyof Obj] 也不检索“a”?这个概念叫什么?如果没有 [keyof Obj] 和稍后调用 keyof,“a”就会在那里。

让我们删除[keyof Obj]

const MyObject = {
    a: 'a',
    b1: 'b1',
    b2: 'b2',
} as const;

type AllowedKeys<Obj> = {
    [K in keyof Obj]: Obj[K] extends `b${infer _}` ? K : never; // <-- use K instead of `b{infer Rest}`
};

{
    // {
    //     readonly a: never;
    //     readonly b1: "b1";
    //     readonly b2: "b2";
    // }
    type _ = AllowedKeys<typeof MyObject>
}

您可能已经注意到,我们正在获取一个对象,如果值存在,则它是允许的键。如果不允许使用密钥,则为 never。这就是为什么anever。现在。让我们尝试在结果中使用方括号表示法。它适用于类型的方式与适用于纯 js 中的运行时值的方式相同:

{
    // {
    //     readonly a: never;
    //     readonly b1: "b1";
    //     readonly b2: "b2";
    // }
    type a = AllowedKeys<typeof MyObject>['a'] // never
    type b = AllowedKeys<typeof MyObject>['b1'] // b1, because keys and allowed values are the same
    type ab = AllowedKeys<typeof MyObject>['a' | 'b1'] // still "b1" because any union with never does not make any sense
}

如您所见,ab 类型返回 b1 而不是 b1 | never。为什么? Here你会找到答案的。因此使用 AllowedKeys&lt;typeof MyObject&gt;[keyof typeof MyObject] retuns b1 | b2 因为 TS 编译器删除了 never

还有一点,返回联合类型不是键的联合,而是对象值的联合。

这里有一个实用程序类型来获取所有对象值的联合:type Values&lt;T&gt;=T[keyof T]

【讨论】:

  • 为什么映射类型AllowedKeys&lt;Obj&gt;末尾的[keyof Obj]不也检索"a"?这个概念叫什么?如果没有[keyof Obj] 和稍后调用keyof"a" 就会在那里。 Playground
  • @Andru 我更新了。希望现在很清楚
  • 哇!感谢您的详尽解释!现在完全有道理!通过[keyof Obj](在本例中为['a' | 'b1' | 'b2']),您正在创建一个联合,因此类型为never 的联合成员将被删除。酷!
【解决方案2】:

在属性键和属性值是相同字符串的情况下限制对象的(而不是值)的解决方案。

这将只选择键以"b" 字母开头的属性(MyObject 问题中的定义):

type OnlyBKeys = {
    [K in keyof typeof MyObject as K extends `b${infer _}` ? K : never]: typeof MyObject[K];
};

或任意前置字符串的通用变体:

type KeyStartsWith<Object extends object, Prepend extends string> = {
    [K in keyof Object as K extends `${Prepend}${infer _}` ? K : never]: Object[K];
};

这可以用作:

const MyObject3: KeyStartsWith<typeof MyObject, 'b'> = {
    b1: 'b1',
    b2: 'b2',
};

TS Playground of solution

【讨论】:

  • 不错的解决方案!我认为值得添加指向key remapping的链接
猜你喜欢
  • 2011-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多