如果我想在Javascript中以编程方式将一个属性分配给一个对象,我会这样做:

var obj = {};
obj.prop = "value";

但在TypeScript中,这会产生一个错误:

属性“prop”在类型为“{}”的值上不存在

我应该如何在TypeScript中分配任何新属性给对象?


当前回答

索引类型

可以将obj表示为任意值,但这违背了使用typescript的全部目的。obj ={}表示obj是一个对象。把它标记为任何都没有意义。为了实现所需的一致性,可以按照如下方式定义接口。

interface LooseObject {
    [key: string]: any
}

var obj: LooseObject = {};

或使之紧凑:

var obj: {[k: string]: any} = {};

LooseObject可以接受任何字符串作为键,任何类型作为值的字段。

obj.prop = "value";
obj.prop2 = 88;

这个解决方案的真正优雅之处在于,您可以在接口中包含类型安全字段。

interface MyType {
    typesafeProp1?: number,
    requiredProp1: string,
    [key: string]: any
}

var obj: MyType ;
obj = { requiredProp1: "foo"}; // valid
obj = {} // error. 'requiredProp1' is missing
obj.typesafeProp1 = "bar" // error. typesafeProp1 should be a number

obj.prop = "value";
obj.prop2 = 88;

记录<Keys,Type>实用程序类型

更新(2020年8月):@transang在评论中提到了这一点

Record<Keys,Type>是typescript中的Utility类型。对于属性名未知的键-值对,它是一种更简洁的替代方法。 值得注意的是,Record<Keys,Type>是{[k: Keys]: Type}的命名别名,其中Keys和Type是泛型。 在我看来,这一点值得一提

相比较而言,

var obj: {[k: string]: any} = {};

就变成了

var obj: Record<string,any> = {}

MyType现在可以通过扩展Record类型来定义

interface MyType extends Record<string,any> {
    typesafeProp1?: number,
    requiredProp1: string,
}

虽然这回答了最初的问题,但@GreeneCreations的回答可能会从另一个角度来解决这个问题。

其他回答

你可以用这个:

this.model = Object.assign(this.model, { newProp: 0 });

晚不过,简单回答

let prop = 'name';
let value = 'sampath';
this.obj = {
   ...this.obj,
   [prop]: value
};

通过将任何类型的对象类型转换为'any'来存储任何新属性:

var extend = <any>myObject;
extend.NewProperty = anotherObject;

稍后,你可以通过将扩展对象转换回'any'来检索它:

var extendedObject = <any>myObject;
var anotherObject = <AnotherObjectType>extendedObject.NewProperty;

这是Object的一个特殊版本。赋值,它会在每次属性更改时自动调整变量类型。不需要额外的变量、类型断言、显式类型或对象副本:

function assign<T, U>(target: T, source: U): asserts target is T & U {
    Object.assign(target, source)
}

const obj = {};
assign(obj, { prop1: "foo" })
//  const obj now has type { prop1: string; }
obj.prop1 // string
assign(obj, { prop2: 42 })
//  const obj now has type { prop1: string; prop2: number; }
obj.prop2 // number

//  const obj: { prop1: "foo", prop2: 42 }

注意:示例使用TS 3.7断言函数。与Object.assign不同,assign的返回类型是void。

当一个Map可以接受固定类型的任意值时,使用ES6 Map,否则使用可选属性

我想这就是我的指导方针。ES6映射可以在typescript中完成,如在typescript中提到的:ES6映射

可选属性的主要用例是函数的“选项”参数:使用命名参数JavaScript(基于typescript)在这种情况下,我们确实提前知道允许的属性的确切列表,所以最明智的事情是只定义一个显式接口,只是使任何可选的可选与?正如在https://stackoverflow.com/a/18444150/895245上提到的,尽可能多地进行类型检查:

const assert = require('assert')

interface myfuncOpts {
  myInt: number,
  myString?: string,
}

function myfunc({
  myInt,
  myString,
}: myfuncOpts) {
  return `${myInt} ${myString}`
}

const opts: myfuncOpts = { myInt: 1 }
if (process.argv.length > 2) {
  opts.myString = 'abc'
}

assert.strictEqual(
  myfunc(opts),
  '1 abc'
)

然后,当它是真正任意的(无限多个可能的键)并且具有固定类型时,我将使用Map,例如:

const assert = require('assert')
const integerNames = new Map<number, string>([[1, 'one']])
integerNames.set(2, 'two')
assert.strictEqual(integerNames.get(1), 'one')
assert.strictEqual(integerNames.get(2), 'two')

测试:

  "dependencies": {
    "@types/node": "^16.11.13",
    "typescript": "^4.5.4"
  }