ChatGPT解决这个技术问题 Extra ChatGPT

如何在 TypeScript 中为对象动态分配属性?

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

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

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

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

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


A
Akash

索引类型

可以将 obj 表示为 any,但这违背了使用 typescript 的全部目的。 obj = {} 暗示 objObject。将其标记为 any 没有意义。为了实现所需的一致性,可以如下定义接口。

interface LooseObject {
    [key: string]: any
}

var obj: LooseObject = {};

或使其紧凑:

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

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

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;

Record 实用程序类型

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

Record 是打字稿中的实用程序类型。对于属性名称未知的键值对,它是一种更简洁的替代方案。值得注意的是 Record 是 {[k: Keys]: Type} 的命名别名,其中 Keys 和 Type 是泛型。 IMO,这在这里值得一提

为了比较,

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

变成

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

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

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

虽然这回答了原始问题,但@GreeneCreations 的答案可能会为如何解决问题提供另一种观点。


我认为这是现在最好的解决方案。我认为当时有人问这样的问题索引属性尚未在 TypeScript 中实现。
当您拥有动态数据时,它确实有意义。如果您从 API 接收动态数据,构建表单,然后将动态值发送回 api,这确实有意义。 Any 是有原因的,显然你应该强输入大多数不是动态的东西,但在动态情况下你不能强输入。
很好的解决方案。漂亮又简单
现在,您可以写 Record<string, any>,而不是 {[key: string]: any}
8 年后,这个答案拯救了我的一天!谢谢!
C
Crwth

或者一口气:

  var obj:any = {}
  obj.prop = 5;

如果我必须将这么多东西强制转换为 any 才能使用它,那么 TypeScript 的意义何在?只是在我的代码中变成了额外的噪音..:/
@AjaxLeung 您应该很少转换为 any。 TypeScript 用于在编译时捕获(潜在的)错误。如果您强制转换为 any 以消除错误,那么您将失去打字的能力,还不如回到纯 JS。理想情况下,仅当您要导入无法编写 TS 定义的代码或将代码从 JS 迁移到 TS 时,才应在理想情况下使用 any
j
jmvtrinidad

当您的对象具有特定类型时,此解决方案很有用。就像将对象获取到其他来源时一样。

let user: User = new User();
(user as any).otherProperty = 'hello';
//user did not lose its type here.

这是一次性作业的正确解决方案。
这个答案对我有用,因为对于 facebook 登录,我必须向窗口对象添加属性。我第一次在 TypeScript 中使用 as 关键字。
D
David Gardiner

我倾向于把 any 放在另一边,即 var foo:IFoo = <any>{}; 所以像这样的东西仍然是类型安全的:

interface IFoo{
    bar:string;
    baz:string;
    boo:string;     
}

// How I tend to intialize 
var foo:IFoo = <any>{};

foo.bar = "asdf";
foo.baz = "boo";
foo.boo = "boo";

// the following is an error, 
// so you haven't lost type safety
foo.bar = 123; 

或者,您可以将这些属性标记为可选:

interface IFoo{
    bar?:string;
    baz?:string;
    boo?:string;    
}

// Now your simple initialization works
var foo:IFoo = {};

Try it online


+1 是唯一保持类型安全的解决方案。只需确保在它之后直接实例化所有非可选属性,以避免稍后出现错误。
这真的有效吗?编译后,我的 javascript 中仍然有 {}。
I still have <any>{ 那么你还没有编译。 TypeScript 会在它的发射中删除它
这些天来,var foo: IFoo = {} as any 是首选。用于类型转换的旧语法与 TSX(Typescript-ified JSX)发生冲突。
我有点困惑,<any>{} 如何尊重 IFoo 的类型?
B
Brandon McConnell

尽管编译器抱怨它仍应按您的要求输出它。但是,这将起作用。

const s = {};
s['prop'] = true;

是的,这不是真正的类型脚本方式,你失去了智能感知。
适用于具有动态结构的对象(不能在接口中定义)
不要使用 var.. 而是使用 let 或 const
G
GreeneCreations

我很惊讶没有一个答案引用 Object.assign,因为这是我在考虑 JavaScript 中的“组合”时使用的技术。

它在 TypeScript 中按预期工作:

interface IExisting {
    userName: string
}

interface INewStuff {
    email: string
}

const existingObject: IExisting = {
    userName: "jsmith"
}

const objectWithAllProps: IExisting & INewStuff = Object.assign({}, existingObject, {
    email: "jsmith@someplace.com"
})

console.log(objectWithAllProps.email); // jsmith@someplace.com

优点

类型安全,因为您根本不需要使用 any 类型

使用 TypeScript 的聚合类型(在声明 objectWithAllProps 的类型时用 & 表示),它清楚地表明我们正在即时(即动态地)组合一个新类型

需要注意的事项

Object.assign 有它自己独特的方面(大多数有经验的 JS 开发人员都知道),在编写 TypeScript 时应该考虑这些方面。它可以以可变方式或不可变方式使用(我在上面演示了不可变方式,这意味着 existingObject 保持不变,因此没有电子邮件属性。对于大多数函数式程序员来说,这是一件好事,因为结果是唯一的新变化)。当你有更扁平的对象时,Object.assign 效果最好。如果您正在组合两个包含可为空属性的嵌套对象,您最终可能会用未定义覆盖真实值。如果你注意 Object.assign 参数的顺序,你应该没问题。


对我来说,在您需要使用它来显示数据的情况下,它似乎工作正常,但是当您需要将修改类型的条目添加到数组时,这似乎不太好用。我采用动态组合对象并将其分配在一行中,然后在连续的行中分配动态属性。这让我大部分时间都在那里,所以谢谢你。
A
Aviw

另一种选择是将属性作为集合访问:

变量 obj = {}; obj['prop'] = "值";


这是最简洁的方式。来自 ES6/ES2015 的 Object.assign(obj, {prop: "value"}) 也可以。
A
Alex

您可以使用扩展运算符基于旧对象创建新对象

interface MyObject {
    prop1: string;
}

const myObj: MyObject = {
    prop1: 'foo',
}

const newObj = {
    ...myObj,
    prop2: 'bar',
}

console.log(newObj.prop2); // 'bar'

TypeScript 将推断原始对象的所有字段,VSCode 将执行自动补全等。


很好,但是如果您需要在例如 prop3 中使用 prop2,将很难实现
不确定我是否遵循该声明 - 此示例中没有 prop3
a
assylias

情况1:

var car = {type: "BMW", model: "i8", color: "white"};
car['owner'] = "ibrahim"; // You can add a property:

案例二:

var car:any = {type: "BMW", model: "i8", color: "white"};
car.owner = "ibrahim"; // You can set a property: use any type

H
HamidReza

你可以使用这个:

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

您不需要 this.model =
this.model = { ...this.model, { newProp: 0 }};
J
Jayant Varshney

最简单的将遵循

const obj = <any>{};
obj.prop1 = "value";
obj.prop2 = "another value"

L
LEMUEL ADANE

因为你不能这样做:

obj.prop = 'value';

如果你的 TS 编译器和你的 linter 不严格你,你可以这样写:

obj['prop'] = 'value';

如果您的 TS 编译器或 linter 是严格的,另一个答案是类型转换:

var obj = {};
obj = obj as unknown as { prop: string };
obj.prop = "value";

这是如果 tsconfig.json 上的 'noImplicitAny: false'
否则你可以做 GreeneCreations 回答。
f
ford04

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

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 }

注意:sample 使用 TS 3.7 assertion functionsassign 的返回类型是 void,与 Object.assign 不同。


b
bersling

要保证类型是 Object(即键值对),请使用:

const obj: {[x: string]: any} = {}
obj.prop = 'cool beans'

这个解决方案对我有用,因为没有额外的类型信息,我仍然会收到这个错误:元素隐式具有“任何”类型,因为“字符串”类型的表达式不能用于索引类型“{}”。
D
Daniel Dietrich

可以通过以下方式将成员添加到现有对象

扩大类型(阅读:扩展/专门化接口)将原始对象转换为扩展类型将成员添加到对象

interface IEnhancedPromise<T> extends Promise<T> {
    sayHello(): void;
}

const p = Promise.resolve("Peter");

const enhancedPromise = p as IEnhancedPromise<string>;

enhancedPromise.sayHello = () => enhancedPromise.then(value => console.info("Hello " + value));

// eventually prints "Hello Peter"
enhancedPromise.sayHello();

s
sampath kumar

迟到了,简单的回答

`

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

`


I
Isidro Martínez

最佳做法是使用安全输入,我建议您:

interface customObject extends MyObject {
   newProp: string;
   newProp2: number;
}

E
Erlend Robaye

通过将其类型转换为“any”,将任何新属性存储在任何类型的对象上:

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

稍后您可以通过将扩展对象转换回“任何”来检索它:

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

这完全是正确的解决方案。假设你有一个对象 let o : ObjectType; .... 稍后您可以将 o 转换为任何 (o).newProperty = 'foo';它可以像 (o).newProperty 一样检索。没有编译器错误,并且像魅力一样工作。
这会阻止智能感知......除了保持智能感知之外,还有什么办法吗?
A
Andre Vianna

要保留您以前的类型,请将您的对象临时转换为任何

  var obj = {}
  (<any>obj).prop = 5;

新的动态属性仅在您使用强制转换时可用:

  var a = obj.prop; ==> Will generate a compiler error
  var b = (<any>obj).prop; ==> Will assign 5 to b with no error;

I
Imtiaz Shakil Siddique

为 Angular 扩展 @jmvtrinidad 解决方案,

当使用已经存在的类型对象时,这是添加新属性的方法。

let user: User = new User();
(user as any).otherProperty = 'hello';
//user did not lose its type here.

现在,如果您想在 html 端使用 otherProperty,这就是您所需要的:

<div *ngIf="$any(user).otherProperty">
   ...
   ...
</div>

当使用 <any>as any 强制转换时,Angular 编译器将 $any() 视为对 any 类型的强制转换,就像在 TypeScript 中一样。


N
Nerdroid

在 TypeScript 中为对象动态分配属性。

要做到这一点你只需要像这样使用打字稿接口:

interface IValue {
    prop1: string;
    prop2: string;
}

interface IType {
    [code: string]: IValue;
}

你可以这样使用它

var obj: IType = {};
obj['code1'] = { 
    prop1: 'prop 1 value', 
    prop2: 'prop 2 value' 
};

我尝试使用您的代码,但没有收到任何错误:pastebin.com/NBvJifzN
尝试初始化 SomeClass 内的 attributes 字段,这应该可以修复它public attributes: IType = {}; pastebin.com/3xnu0TnN
f
fregante

唯一完全类型安全的解决方案是 this one,但有点罗嗦,并迫使您创建多个对象。

如果您必须首先创建一个空对象,然后选择这两种解决方案之一。请记住,每次使用 as 时,您都会失去安全感。

更安全的解决方案

object 的类型在 getObject 中是 safe,这意味着 object.a 将是 string | undefined 类型

interface Example {
  a: string;
  b: number;
}

function getObject() {
  const object: Partial<Example> = {};
  object.a = 'one';
  object.b = 1;
  return object as Example;
}

简短的解决方案

object 的类型在 getObject 中是不安全的,这意味着 object.a 甚至在分配之前就是 string 类型。

interface Example {
  a: string;
  b: number;
}

function getObject() {
  const object = {} as Example;
  object.a = 'one';
  object.b = 1;
  return object;
}

J
Jack Punt

如果你使用的是 Typescript,想必你想使用类型安全;在这种情况下,裸对象和“任何”被反指示。

最好不要使用 Object 或 {},而是使用一些命名类型;或者您可能正在使用具有特定类型的 API,您需要使用自己的字段进行扩展。我发现这个工作:

class Given { ... }  // API specified fields; or maybe it's just Object {}

interface PropAble extends Given {
    props?: string;  // you can cast any Given to this and set .props
    // '?' indicates that the field is optional
}
let g:Given = getTheGivenObject();
(g as PropAble).props = "value for my new field";

// to avoid constantly casting: 
let k = getTheGivenObject() as PropAble;
k.props = "value for props";

P
Pang

您可以添加此声明以使警告静音。

declare var obj: any;

R
Rafał Cieślak

我在尝试对充当状态存储的对象进行部分更新时遇到了这个问题。

type State = {
  foo: string;
  bar: string;
  baz: string;
};

const newState = { foo: 'abc' };

if (someCondition) {
  newState.bar = 'xyz'
}

setState(newState);

在这种情况下,最好的解决方案是使用 Partial<T>。它使用 ? 标记使所提供类型的所有属性都是可选的。在 a more specific SO topic about making all properties on a type optional 中了解更多信息。

以下是我使用 Partial<T> 解决的方法:

type State = {
  foo: string;
  bar: string;
  baz: string;
};

const newState: Partial<State> = { foo: 'abc' };

if (someCondition) {
  newState.bar = 'xyz';
}

setState(newState);

这类似于 fregante 在他们的回答中描述的内容,但我想为这个特定的用例(这在前端应用程序中很常见)画出更清晰的画面。


D
Danilo Carrabino

我写了一篇文章来解决这个话题:

Typescript – 在运行时增强对象及其类型

https://tech.xriba.io/2022/03/24/typescript-enhance-an-object-and-its-type-at-runtime/

也许您可以从 Typescript 概念中获得灵感,例如:

映射类型

通过 as 进行密钥重映射

交叉口类型


C
Ciro Santilli Путлер Капут 六四事

只要地图可以采用固定类型的真正任意值,则使用 ES6 Map,否则使用可选属性

我认为这是我要遵循的指导方针。 ES6 映射可以在 typescript 中完成,如下所述:ES6 Map in Typescript

可选属性的主要用例是函数的“选项”参数:Using named parameters JavaScript (based on 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"
  }

T
Tung Nguyen

尝试这个:

export interface QueryParams {
    page?: number,
    limit?: number,
    name?: string,
    sort?: string,
    direction?: string
}

然后使用它

const query = {
    name: 'abc'
}
query.page = 1