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

Posted

技术标签:

【中文标题】如何在 TypeScript 中为对象动态分配属性?【英文标题】:How do I dynamically assign properties to an object in TypeScript? 【发布时间】:2015-06-29 12:00:50 【问题描述】:

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

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

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

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

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

【问题讨论】:

【参考方案1】:

索引类型

可以将obj 表示为any,但这违背了使用打字稿的全部目的。 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<Keys,Type> 实用程序类型

更新(2020 年 8 月):@transang 在 cmets 中提出了这个问题

Record<Keys,Type> 是 typescript 中的 Utility 类型。对于属性名称未知的键值对来说,它是一种更简洁的替代方案。 值得注意的是Record<Keys,Type>[k: Keys]: Type 的命名别名,其中KeysType 是泛型。 IMO,这值得在这里提及

为了比较,

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

变成

var obj: Record<string,any> = 

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

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

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

【讨论】:

我认为这是目前最好的解决方案。我认为当时被问到这个问题的索引属性还没有在 TypeScript 中实现。 当你有动态数据时它确实有意义。如果您从 API 接收动态数据,构建表单,然后将动态值发送回 api,这确实有意义。 Any 是有原因的,显然你应该强输入大多数不是动态的东西,但在动态情况下你不能强输入。 很好的解决方案。漂亮又简单 现在,你可以写Record&lt;string, any&gt;,而不是[key: string]: any 8 年后,这个答案拯救了我的一天!谢谢!【参考方案2】:

或一次性完成:

  var obj:any = 
  obj.prop = 5;

【讨论】:

如果我必须将这么多东西投射到any 才能使用它,那么 TypeScript 的意义何在?只是在我的代码中变成了额外的噪音..:/ @AjaxLeung 如果你需要这样做,那是你用错了。 查看上面的附加编辑,保留对象以前的类型。 @AjaxLeung 你应该很少投到any。 TypeScript 用于在编译时捕获(潜在的)错误。如果你使用any 来消除错误,那么你就失去了打字的能力,还不如回到纯JS。 any 理想情况下只应在您导入无法编写 TS 定义的代码或将代码从 JS 迁移到 TS 时使用【参考方案3】:

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

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

【讨论】:

这是一次性作业的正确解决方案。 这个答案对我有用,因为对于 facebook 登录,我必须向 window 对象添加属性。我第一次在 TypeScript 中使用 as 关键字。【参考方案4】:

我倾向于将any 放在另一边,即var foo:IFoo = &lt;any&gt;; 所以这样的东西仍然是类型安全的:

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 &lt;any&gt; 那么你还没有编译。 TypeScript 会在它的发射中删除它 如今,var foo: IFoo = as any 是首选。用于类型转换的旧语法与 TSX(Typescript-ified JSX)发生冲突。【参考方案5】:

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

var s = ;
s['prop'] = true;

【讨论】:

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

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

var obj = ;
obj['prop'] = "value";

【讨论】:

这是最简洁的方式。来自 ES6/ES2015 的 Object.assign(obj, prop: "value") 也可以。【参考方案7】:

我很惊讶没有一个答案引用 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 的类型时由 &amp; 表示),这清楚地表明我们正在即时(即动态)组合新类型

注意事项

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

【讨论】:

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

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

interface MyObject 
    prop1: string;


const myObj: MyObject = 
    prop1: 'foo',


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


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

TypeScript 会推断出原始对象的所有字段,VSCode 会做自动补全等。

【讨论】:

很好,但如果您需要在例如使用 prop2 prop3,将难以实施 不确定我是否遵循该声明 - 在此示例中没有 prop3【参考方案9】:

你可以用这个:

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

【讨论】:

你不需要this.model = this.model = ...this.model, newProp: 0 ;【参考方案10】:

因为你不能这样做:

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 的回答。【参考方案11】:

最简单的会跟着

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

【讨论】:

【参考方案12】:

这是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 functions。 assign 的返回类型是 void,与 Object.assign 不同。

【讨论】:

【参考方案13】:

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

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

【讨论】:

这个解决方案对我有用,因为没有额外的类型信息,我仍然得到这个错误:元素隐式地有一个'任何'类型,因为类型'字符串'的表达式不能用于索引类型' '。【参考方案14】:

案例一:

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

案例 2:

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

【讨论】:

【参考方案15】:

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

    扩大类型(阅读:扩展/专门化接口) 将原始对象转换为扩展类型 将成员添加到对象
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();

【讨论】:

【参考方案16】:

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

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

【讨论】:

【参考方案17】:

通过将其类型转换为“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 一样检索。没有编译器错误,工作起来就像一个魅力。 这会阻碍智能感知......除了保持智能感知之外,有什么办法可以解决这个问题吗?【参考方案18】:

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

  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;

【讨论】:

【参考方案19】:

为 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>

Angular 编译器将$any() 视为any 类型的转换,就像在TypeScript 中使用&lt;any&gt;as any 转换时一样。

【讨论】:

【参考方案20】:

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

要做到这一点,您只需要像这样使用 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【参考方案21】:

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

declare var obj: any;

【讨论】:

【参考方案22】:

如果你使用的是 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:PropAble = getTheGivenObject();
k.props = "value for props";

【讨论】:

【参考方案23】:

试试这个:

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

那就用吧

const query = 
    name: 'abc'

query.page = 1

【讨论】:

【参考方案24】:

唯一完全类型安全的解决方案是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;

【讨论】:

以上是关于如何在 TypeScript 中为对象动态分配属性?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 TypeScript 中为类属性动态分配值

如何在node.js中为对象动态添加属性?

如何在TypeScript中使用动态对象属性

如何在 C++ 中为二维对象数组分配内存? [复制]

如何在C中为char**动态分配内存

如何在 Qt 中为场景中的对象设置属性