用不同类型的属性扩展`Record <string,string []>`

时间:2019-07-25 18:40:00

标签: typescript

在打字稿中,以下内容:

interface RecordX extends Record<string, string[]> {
  id: string
}

抱怨:

  

类型'string'的属性'id'不能分配给字符串索引类型'string []'。 ts(2411)

如何将不同类型的属性添加到Record<>实用程序类型?

编辑-详细信息和一般案例+样本

更笼统地说,这个问题可能是:如何描述具有异构类型固定属性但动态添加的同类类型属性的对象。

例如给出此对象的示例:

const a = {
   id: '123',
   count: 123,
   set: new Set(),
   dynamicItemList: ['X', 'Y']
   anotherDynamicallyAddedList: ['Y', 'Z']
} as ExtensibleRecord

对于类型ExtensibleRecord

  1. idcountset的类型和属性键是固定的,在编译时是已知的
  2. dynamicItemListanotherDynamicallyAddedList的类型在编译时是已知的,但是它们在对象上的键却不是

我尝试了许多我认为可能可行的变体,包括:

type ExtensibleRecord = {
  id: string, count: number, set: Set
} & Record<string, string[]>

type ExtensibleRecord = {
  id: string, count: number, set: Set
} & Omit<Record<string, string[]>, 'id'|'count'|'set'>

interface ExtensibleRecord = {
  id: string,
  count: number,
  set: Set,
  [k: string]: string[]
}

但每个似乎都会导致错误。

这感觉很普通,很明显,但是我找不到示例或参考。

Obligatory playground

3 个答案:

答案 0 :(得分:2)

无法通过官方docs来实现您想要的目标:

  

虽然字符串索引签名是描述   “字典”模式,它们还强制所有属性匹配   他们的返回类型。这是因为字符串索引声明   obj.property也可以作为obj [“ property”]使用。在下面的   例如,名称的类型与字符串索引的类型不匹配,并且   类型检查器给出错误:

interface NumberDictionary {
    [index: string]: number;
    length: number;    // ok, length is a number
    name: string;      // error, the type of 'name' is not a subtype of the indexer
}

但是,根据此site,在以下情况下可以从索引签名中排除某些属性:当您要为声明的变量建模时。我正在从site复制整个部分。

  

有时您需要将属性组合到索引签名中。   不建议这样做,您应该使用嵌套索引签名   上面提到的模式。但是,如果您要建模现有的   您可以使用JavaScript使用交叉点类型来解决它。的   以下显示的是您将遇到的错误的示例   使用路口:

type FieldState = {
  value: string
}

type FormState = {
  isValid: boolean  // Error: Does not conform to the index signature
  [fieldName: string]: FieldState
}
     

以下是使用交叉点类型的解决方法:

type FieldState = {
  value: string
}

type FormState =
  { isValid: boolean }
  & { [fieldName: string]: FieldState }
     

请注意,即使您可以声明它来建模现有的JavaScript,   您不能使用TypeScript创建这样的对象:

type FieldState = {
  value: string
}

type FormState =
  { isValid: boolean }
  & { [fieldName: string]: FieldState }


// Use it for some JavaScript object you are gettting from somewhere 
declare const foo:FormState; 

const isValidBool = foo.isValid;
const somethingFieldState = foo['something'];

// Using it to create a TypeScript object will not work
const bar: FormState = { // Error `isValid` not assignable to `FieldState
  isValid: false
}

最后,作为一种解决方法,如果合适的话,您可以创建一个嵌套接口(检查site的“设计模式:嵌套索引签名”部分,其中动态字段位于该接口的属性中)例如。

interface RecordX {
  id: string,
  count: number,
  set: Set<number>,
  dynamicFields: {
    [k: string]: string[]
  }
}

答案 1 :(得分:1)

如果您没有将其用于class,则可以使用type对其进行描述:

type RecordWithID = Record<string, string[]> & {
  id: string
}

let x: RecordWithID = {} as any

x.id = 'abc'

x.abc = ['some-string']

http://www.typescriptlang.org/play/#code/C4TwDgpgBAShDGB7ATgEwOoEtgAsCSAIlALywIqoA8AzsMpgHYDmANFLfcwNoC6AfFABkUAN4BYAFBQomVAC52dRk0kBfSZIA2EYFAAeCuEjRZchEqNVQAhtRsMQGiXoB0siwHJrAI3genrj7wFlxe1KgAZh48TkA

答案 2 :(得分:1)

佩德罗的回答是100%正确的,这是我旅程的开始,谢谢!

我想找到最简单的解决方法,并找到一种解决方案:Object.assign()。似乎除了手动构造这些对象的类型错误之外,甚至在使用扩展运算符时也可能除外,这可能是带有本机JavaScript功能的TypeScript互操作。

在工厂函数中包装Object.assign()与并行类型结合起来对我来说很优雅,也许对其他人很有用。

// FACTORY WITH TYPE

// Factory uses Object.assign(), which does not error, and returns intersection type
function RecordX(
  fixedProps: { id: string },
  dynamicProps?: Record<string, string[]>
) {
  return Object.assign({}, dynamicProps, fixedProps);
}

// Type name conveniently overlaps with factory and resolves to:
// 
// type RecordX = Record<string, string[]> & {
//   id: string;
// }
type RecordX = ReturnType<typeof RecordX>;

// USAGE

// Standard use
const model: RecordX = RecordX({ id: 'id-1' }, {
  foo: ['a'],
});

// Correct type: string
const id = model.id;

// Correct type: string[]
const otherProp = model.otherProp;

// Appropriately errors with "string is not assignable to string[]"
const model2: RecordX = RecordX({ id: 'id-2' }, {
  bar: 'b'
});