打字稿:声明对象上的所有属性必须具有相同的类型

时间:2018-07-09 02:48:27

标签: typescript types interface type-hinting type-safety

在Typescript中,您可以声明数组中的所有元素都具有相同的类型,如下所示:

return combineLatest(this.itemDoc.valueChanges, this.authU.user)
    .pipe(
        map(([response, authUser]) => {
            return response.users.any(x => x.userID === authUser.uid);
        })
    );

您是否可以执行类似的操作来声明一个对象的所有属性值都必须为同一类型? (不指定每个属性名称)

例如,我目前正在这样做:

const theArray: MyInterface[]

...请注意如何为每个属性指定interface MyInterface { name:string; } const allTheThingsCurrently = { first: <MyInterface>{name: 'first thing name' }, second: <MyInterface>{name: 'second thing name' }, third: <MyInterface>{name: 'third thing name' }, //... }; 。有什么捷径吗?即,我正在想像这样的事情...

<MyInterface>

const allTheThingsWanted:MyInterface{} = { first: {name: 'first thing name' }, second: {name: 'second thing name' }, third: {name: 'third thing name' }, //... }; 是无效代码的一部分,我正在寻找一种方法来减少冗余,并选择额外的严格性以防止任何其他属性被添加到不同类型的对象中。

4 个答案:

答案 0 :(得分:7)

解决方案1:Indexable type

interface Thing {
  name: string
}

interface ThingMap {
  [thingName: string]: Thing
}

const allTheThings: ThingMap = {
  first: { name: "first thing name" },
  second: { name: "second thing name" },
  third: { name: "third thing name" },
}

这里的缺点是您可以访问allTheThings之外的任何属性,而不会出现任何错误:

allTheThings.nonexistent // type is Thing

可以通过将ThingMap定义为[thingName: string]: Thing | void来提高安全性,但这将要求在整个位置进行空检查,即使您访问的是那里的属性也是如此。

解决方案2:具有无操作功能的泛型

const createThings = <M extends ThingMap>(things: M) => things

const allTheThings = createThings({
  first: { name: "first thing name" },
  second: { name: "second thing name" },
  third: { name: "third thing name" },
  fourth: { oops: 'lol!' }, // error here
})

allTheThings.first
allTheThings.nonexistent // comment out "fourth" above, error here

createThings函数具有通用的M,并且M可以是任意值,只要所有值均为Thing,则它返回{{1} }。传入对象时,它将针对M之后的类型验证对象,同时返回与传入对象相同的形状。

这是“最智能”的解决方案,但是使用了一个看起来很聪明的hack使其真正起作用。无论如何,在TS添加更好的模式来支持此类情况之前,这将是我的首选路线。

答案 1 :(得分:4)

一些单层(平面)对象的替代方案:

替代1(indexable type):

exampleObj: { [k: string]: string }) = {
  first: 'premier',
  second: 'deuxieme',
  third: 'troisieme',
}

替代2(Record):

exampleObj: Record<string, string>) = {
  first: 'premier',
  second: 'deuxieme',
  third: 'troisieme',
}

替代3(Record / Union):

exampleObj: Record<'first' | 'second' | 'third', string>) = {
  first: 'premier',
  second: 'deuxieme',
  third: 'troisieme',
}

答案 2 :(得分:1)

使用泛型并指定所需的属性类型。

type SamePropTypeOnly<T> = {
  [P: string]: T;
}

interface MyInterface {
  name: string;
}

const newObj: SamePropTypeOnly<MyInterface> = {
  first: { name: 'first thing name' },
  second: { name: 'second thing name' },
  third: { name: 'third thing name' },
  // forth: 'Blah' // Type 'string' is not assignable to type `MyInterface`
}

答案 3 :(得分:0)

方法 Generics with a no-op function 可以扩展为具有接受某种类型的必需值的通用函数,该函数本身返回无操作函数。 这样就不需要为每种类型创建新函数

export const typedRecord = <TValue>() => <T extends Record<PropertyKey, TValue>>(v: T): T => v;

这涵盖了所有要求

const allTheThings = typedRecord<Thing>()({
  first: { name: "first thing name" },
  second: { name: "second thing name" },
  third: { name: "third thing name" },
  fourth: { oops: "lol!" }, // error here
});

allTheThings.first;
allTheThings.nonexistent; // error here