typescript 指定值的类型而不指定对象的键的类型

scyqe7ek  于 2023-02-20  发布在  TypeScript
关注(0)|答案(3)|浏览(178)

我有一个相当复杂的interface,它有许多众所周知的示例。这些示例应该可以通过某种ID引用。理想情况下,我希望在一个对象中定义所有这些众所周知的示例,这样我就可以使用keyof操作符来轻松地为键指定一个类型。

interface Complex { 
  frobs: number[];
  type: 'A' | 'B';
} // More complicated in reality

const COMMON = {
  foo: { type: 'A', frobs: [] },           // this is fine
  bar: { type: 'B', frobs: [1], zorg: 1 }, // unwanted extra
  baz: { type: 'A', noFrobs: "uh-oh" },    // frobs missing
}

Type CommonId = keyof typeof COMMON; // Great, valid Ids are "foo" | "bar" | "baz"

但是,如果我指定了没有任何类型的对象(如上所述),Typescript当然不会强制该对象中的所有值都应该是同一类型。
所以我想指定所有的值都必须是Complex类型,但是如果不指定键类型,我就无法做到这一点,现在我必须对每个键重复一次:一次作为CommonId的一部分,一次在对象中:

interface Complex { 
  frobs: number[]; 
  type: 'A' | 'B';
} // More complicated in reality
type CommonId = "foo" | "bar";         // Much more common objects in reality

const COMMON: { 
  [commonId in CommonId]: Complex 
} = {
  foo: { type: 'A', frobs: [] },
  bar: { type: 'B', frobs: [1] }
}

有没有办法两全其美呢?我希望用一个新的键-值对扩展COMMON变量,自动将该键添加到CommonId类型中。

bxpogfeg

bxpogfeg1#

你可以先创建一个不带类型的变量,用它来获取键的类型,然后导出相同的值,但是类型如下:

interface Complex { frobs: number[]; }

const _common = {
  foo: { frobs: [] },
  bar: { frobs: [1] }
};

type CommonId = keyof typeof _common;

export const COMMON: { 
  [commonId in CommonId]: Complex 
} = _common;

Playground链接

avwztpqn

avwztpqn2#

    • 更新**

从Typescript 4.9开始,这可以通过satisfies关键字轻松完成:

const COMMON = {
  foo: { type: 'A', frobs: [] },          
  bar: { type: 'B', frobs: [1], zorg: 1 },
                            //  ~~~~~~~ <- ERROR
  baz: { type: 'A', noFrobs: "uh-oh" },
                //  ~~~~~~~~~~~~~~~~ <- ERROR
} satisfies Record<string, Complex>;

type CommonId = keyof typeof COMMON; // Good, it's still "foo" | "bar" | "baz"
    • 原始答案**

这是一个进一步简化的解决方案(主要归功于Aplet123,因为它受到了他的回答,特别是他的评论的启发,只有我写的一个额外的泛型函数)。我的用例实际上是定义一个配置,因此makeConfig的名称。

export function makeConfig<C>() {
  return <K extends string>(cfg: Record<K, C>) => cfg;
}

如果有用的话,你可以导出并重用一个通用类型的config函数:

interface Complex {
  frobs: number[];
  type: 'A' | 'B';
}

export const complexConfig = makeConfig<Complex>();

然后像这样简单地使用它:

export const COMMON = complexConfig({
  foo: { type: 'A', frobs: [] },
  bar: { type: 'B', frobs: [1] }
});

(Note不需要像CommonId这样的其他变量/类型声明,除非您想导出它们)
或者,如果您只想对Complex类型使用一次:

export const COMMON = makeConfig<Complex>()({
  foo: { type: 'A', frobs: [] },
  bar: { type: 'B', frobs: [1] }
});

EDIT:或者稍微简洁一点:

// generic definition
type ValueTypeCheck<C> = <K extends string>(x: Record<K,C>) => Record<K,C>;

const valueType = <C,>() => (x => x) as ValueTypeCheck<C>;

// usage
const complex = valueType<Complex>();

export const COMMON = complex({...});

总的来说,我们确实需要TS中的一个内置机制来强制值的类型,而不使用索引类型,索引类型会丢失关于键名的信息...

twh00eeo

twh00eeo3#

type ValueTypeCheck<T> = <K extends string>(x: Record<K,T>) => Record<K,T>
const valueType = <T>() => (x => x) as ValueTypeCheck<T>
const complex = valueType<YourObjectValueGoesHere>() // use type of object value

export const validationRules = complex({...}) // replace ... with your object

相关问题