332

如何以这种方式在 TS 中初始化一个新类(在 C# 中显示我想要的示例):

// ... some code before
return new MyClass { Field1 = "ASD", Field2 = "QWE" };
// ...  some code after
4

17 回答 17

571

2016 年 7 月 12日更新: Typescript 2.1 引入了 Mapped Types并提供了Partial<T>,它允许您执行此操作....

class Person {
    public name: string = "default"
    public address: string = "default"
    public age: number = 0;

    public constructor(init?:Partial<Person>) {
        Object.assign(this, init);
    }
}

let persons = [
    new Person(),
    new Person({}),
    new Person({name:"John"}),
    new Person({address:"Earth"}),    
    new Person({age:20, address:"Earth", name:"John"}),
];

原答案:

我的方法是定义一个单独的fields变量,然后传递给构造函数。诀窍是将此初始化程序的所有类字段重新定义为可选。创建对象时(使用其默认值),您只需将初始化对象分配到this;

export class Person {
    public name: string = "default"
    public address: string = "default"
    public age: number = 0;

    public constructor(
        fields?: {
            name?: string,
            address?: string,
            age?: number
        }) {
        if (fields) Object.assign(this, fields);
    }
}

或手动进行(更安全一点):

if (fields) {
    this.name = fields.name || this.name;       
    this.address = fields.address || this.address;        
    this.age = fields.age || this.age;        
}

用法:

let persons = [
    new Person(),
    new Person({name:"Joe"}),
    new Person({
        name:"Joe",
        address:"planet Earth"
    }),
    new Person({
        age:5,               
        address:"planet Earth",
        name:"Joe"
    }),
    new Person(new Person({name:"Joe"})) //shallow clone
]; 

和控制台输出:

Person { name: 'default', address: 'default', age: 0 }
Person { name: 'Joe', address: 'default', age: 0 }
Person { name: 'Joe', address: 'planet Earth', age: 0 }
Person { name: 'Joe', address: 'planet Earth', age: 5 }
Person { name: 'Joe', address: 'default', age: 0 }   

这为您提供了基本的安全和属性初始化,但它都是可选的并且可能是无序的。如果你不传递一个字段,你会得到类的默认值。

您也可以将它与所需的构造函数参数混合使用——坚持fields到底。

我认为与您将要获得的 C# 风格差不多(实际的 field-init 语法被拒绝)。我更喜欢适当的字段初始化程序,但看起来它不会发生。

为了比较,如果您使用强制转换方法,您的初始化程序对象必须具有您要强制转换的类型的所有字段,并且不要获得由类本身创建的任何类特定函数(或派生)。

于 2016-06-07T14:38:35.030 回答
111

更新

自从写下这个答案以来,已经出现了更好的方法。请参阅下面有更多投票和更好答案的其他答案。我无法删除此答案,因为它被标记为已接受。


旧答案

TypeScript codeplex 上有一个问题描述了这一点:Support for object initializers

如前所述,您已经可以通过在 TypeScript 中使用接口而不是类来做到这一点:

interface Name {
    givenName: string;
    surname: string;
}
class Person {
    name: Name;
    age: number;
}

var bob: Person = {
    name: {
        givenName: "Bob",
        surname: "Smith",
    },
    age: 35,
};
于 2013-01-03T15:44:29.953 回答
46

下面是一个解决方案,它结合了更短的应用程序Object.assign来更紧密地模拟原始C#模式。

但首先,让我们回顾一下目前提供的技术,其中包括:

  1. 复制接受对象并将其应用于的构造函数Object.assign
  2. Partial<T>复制构造函数中的一个巧妙技巧
  3. 对 POJO 使用“强制转换”
  4. 用杠杆Object.create代替Object.assign

当然,每个人都有自己的优点/缺点。修改目标类以创建复制构造函数可能并不总是一种选择。并且“强制转换”会丢失与目标类型相关的任何功能。 Object.create似乎不太吸引人,因为它需要一个相当冗长的属性描述符映射。

最短的通用答案

因此,这是另一种更简单的方法,它维护类型定义和相关的函数原型,并且更接近地模拟预期的C#模式:

const john = Object.assign( new Person(), {
    name: "John",
    age: 29,
    address: "Earth"
});

而已。该C#模式的唯一添加是Object.assign2 个括号和一个逗号。查看下面的工作示例以确认它维护类型的函数原型。不需要构造函数,也不需要聪明的技巧。

工作示例

此示例显示如何使用字段初始值设定项的近似值来初始化对象C#

class Person {
    name: string = '';
    address: string = '';
    age: number = 0;

    aboutMe() {
        return `Hi, I'm ${this.name}, aged ${this.age} and from ${this.address}`;
    }
}

// typescript field initializer (maintains "type" definition)
const john = Object.assign( new Person(), {
    name: "John",
    age: 29,
    address: "Earth"
});

// initialized object maintains aboutMe() function prototype
console.log( john.aboutMe() );

于 2017-10-21T03:38:24.850 回答
32

您可以影响在您的类类型中强制转换的匿名对象。 奖励:在视觉工作室中,您可以通过这种方式受益于智能感知 :)

var anInstance: AClass = <AClass> {
    Property1: "Value",
    Property2: "Value",
    PropertyBoolean: true,
    PropertyNumber: 1
};

编辑:

警告如果类有方法,你的类的实例不会得到它们。如果 AClass 有构造函数,则不会执行。如果你使用 instanceof AClass,你会得到 false。

总之,您应该使用 interface 而不是 class。最常见的用途是声明为普通旧对象的域模型。实际上,对于域模型,您应该更好地使用接口而不是类。接口在编译时用于类型检查,与类不同,接口在编译期间被完全删除。

interface IModel {
   Property1: string;
   Property2: string;
   PropertyBoolean: boolean;
   PropertyNumber: number;
}

var anObject: IModel = {
     Property1: "Value",
     Property2: "Value",
     PropertyBoolean: true,
     PropertyNumber: 1
 };
于 2015-11-17T08:17:48.433 回答
18

我建议一种不需要 Typescript 2.1 的方法:

class Person {
    public name: string;
    public address?: string;
    public age: number;

    public constructor(init:Person) {
        Object.assign(this, init);
    }

    public someFunc() {
        // todo
    }
}

let person = new Person(<Person>{ age:20, name:"John" });
person.someFunc();

关键点:

  • Typescript 2.1 不需要,Partial<T>不需要
  • 它支持函数(与不支持函数的简单类型断言相比)
于 2017-03-06T05:59:00.800 回答
15

在某些情况下,使用Object.create. 如果您需要向后兼容性或想要滚动您自己的初始化函数,Mozilla 参考包含一个 polyfill。

应用于您的示例:

Object.create(Person.prototype, {
    'Field1': { value: 'ASD' },
    'Field2': { value: 'QWE' }
});

有用的场景

  • 单元测试
  • 内联声明

就我而言,我发现这在单元测试中很有用,原因有两个:

  1. 在测试期望时,我经常想创建一个苗条的对象作为期望
  2. 单元测试框架(如 Jasmine)可能会比较对象原型(__proto__)并导致测试失败。例如:
var actual = new MyClass();
actual.field1 = "ASD";
expect({ field1: "ASD" }).toEqual(actual); // fails

单元测试失败的输出不会产生关于什么不匹配的线索。

  1. 在单元测试中,我可以选择我支持的浏览器

最后,http ://typescript.codeplex.com/workitem/334 提出的解决方案不支持内联 json 样式的声明。例如,以下内容无法编译:

var o = { 
  m: MyClass: { Field1:"ASD" }
};
于 2015-08-11T20:52:09.023 回答
13

您可以拥有一个带有可选字段(标有?)的类和一个接收同一类实例的构造函数。

class Person {
    name: string;     // required
    address?: string; // optional
    age?: number;     // optional

    constructor(person: Person) {
        Object.assign(this, person);
    }
}

let persons = [
    new Person({ name: "John" }),
    new Person({ address: "Earth" }),    
    new Person({ age: 20, address: "Earth", name: "John" }),
];

在这种情况下,您将无法省略必填字段。这使您可以对对象构造进行细粒度控制。

您可以使用具有 Partial 类型的构造函数,如其他答案中所述:

public constructor(init?:Partial<Person>) {
    Object.assign(this, init);
}

问题是所有字段都成为可选的,并且在大多数情况下是不可取的。

于 2019-10-26T16:14:36.043 回答
12

我更倾向于这样做,使用(可选)自动属性和默认值。您没有建议这两个字段是数据结构的一部分,所以这就是我选择这种方式的原因。

您可以拥有类的属性,然后以通常的方式分配它们。显然它们可能需要也可能不需要,所以这也是另外一回事。只是这是非常好的语法糖。

class MyClass{
    constructor(public Field1:string = "", public Field2:string = "")
    {
        // other constructor stuff
    }
}

var myClass = new MyClass("ASD", "QWE");
alert(myClass.Field1); // voila! statement completion on these properties
于 2013-01-09T01:15:15.453 回答
12

我想要一个具有以下内容的解决方案:

  • 所有数据对象都是必需的,并且必须由构造函数填充。
  • 无需提供默认值。
  • 可以使用类内的函数。

这是我的做法:

export class Person {
  id!: number;
  firstName!: string;
  lastName!: string;

  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  }

  constructor(data: OnlyData<Person>) {
    Object.assign(this, data);
  }
}

const person = new Person({ id: 5, firstName: "John", lastName: "Doe" });
person.getFullName();

构造函数中的所有属性都是必需的,如果没有编译器错误,则不能省略。

它取决于OnlyData过滤掉getFullName()所需属性的 ,它的定义如下:

// based on : https://medium.com/dailyjs/typescript-create-a-condition-based-subset-types-9d902cea5b8c
type FilterFlags<Base, Condition> = { [Key in keyof Base]: Base[Key] extends Condition ? never : Key };
type AllowedNames<Base, Condition> = FilterFlags<Base, Condition>[keyof Base];
type SubType<Base, Condition> = Pick<Base, AllowedNames<Base, Condition>>;
type OnlyData<T> = SubType<T, (_: any) => any>;

这种方式的当前限制:

  • 需要 TypeScript 2.8
  • 带有 getter/setter 的类
于 2019-04-23T18:51:01.097 回答
4

要初始化一个类而不重新声明默认值的所有属性:

class MyClass{ 
  prop1!: string  //required to be passed in
  prop2!: string  //required to be passed in
  prop3 = 'some default'
  prop4 = 123

  constructor(opts:{prop1:string, prop2:string} & Partial<MyClass>){
    Object.assign(this,opts)
  }
}

这结合了一些已经很好的答案

于 2020-07-12T06:05:34.990 回答
4

这是另一种解决方案:

return {
  Field1 : "ASD",
  Field2 : "QWE" 
} as myClass;
于 2019-12-04T13:09:37.570 回答
3

这是我为此找到的最佳解决方案。

声明一个可以用作装饰器的函数。我称之为自动反射

export function AutoReflect<T extends { new(...args: any[]): {} }>(
  constructor: T
) {
  return class extends constructor {
    constructor(...args: any[]) {
      super(args)
      if (typeof args[0] === 'object') {
        Object.assign(this, args[0]);
      }
    }
  };
}

这样做是期望构造函数中的一个对象并将成员分配给类实例。在类声明中使用它

interface IPerson {
  name: string;
  age: number;
}

@AutoReflect
class Person implements IPerson {
  name: string;
  number: number;
  constructor(model?: Partial<IPerson>){}
}

在模型的构造函数中,您可以使模型成为可选的,并且在使用 Partial 时,您可以在不设置所有属性值的情况下新建一个实例

new Person({
   name: 'Santa'
});

这个方法创建了一个你想要的类的新实例,并且对它也有一种 C# 对象初始化的感觉。

于 2021-05-18T21:01:18.870 回答
2

最简单的方法是使用类型转换。

return <MyClass>{ Field1: "ASD", Field2: "QWE" };
于 2016-08-07T04:05:45.000 回答
1

如果您使用的是旧版本的 typescript < 2.1,那么您可以使用类似于以下内容的内容,这基本上是将任何类型的对象强制转换为:

const typedProduct = <Product>{
                    code: <string>product.sku
                };

注意:使用此方法仅适用于数据模型,因为它将删除对象中的所有方法。它基本上是将任何对象转换为类型化对象

于 2018-06-01T10:34:58.283 回答
1

这是一个解决方案:

  • 不会强迫您将所有字段设为可选(与 不同Partial<...>
  • 区分类方法和函数类型的字段(与OnlyData<...>解决方案不同)
  • 通过定义 Params 接口提供了一个很好的结构
  • 不需要多次重复变量名称和类型

唯一的缺点是一开始看起来更复杂。


// Define all fields here
interface PersonParams {
  id: string
  name?: string
  coolCallback: () => string
}

// extend the params interface with an interface that has
// the same class name as the target class
// (if you omit the Params interface, you will have to redeclare
// all variables in the Person class)
interface Person extends PersonParams { }

// merge the Person interface with Person class (no need to repeat params)
// person will have all fields of PersonParams
// (yes, this is valid TS)
class Person {
  constructor(params: PersonParams) {
    // could also do Object.assign(this, params);

    this.id = params.id;
    this.name = params.name;

    // intellisence will expect params
    // to have `coolCallback` but not `sayHello`
    this.coolCallback = params.coolCallback;
  }

  // compatible with functions
  sayHello() {
    console.log(`Hi ${this.name}!`);
  }
}

// you can only export on another line (not `export default class...`)
export default Person;
于 2021-01-24T19:46:36.220 回答
0

对于更现代的 TypeScript 版本

类定义

    export class PaymentRequestDto {
      public PaymentSource: number;
      public PaymentCenterUid: string;
      public ConnectedUserUid: string;
    }

你有一些来自某个地方的价值观:

    const PaymentCenter= 'EA0AC01E-D34E-493B-92FF-EB2D66512345';
    const PaymentSource= 4;
    const ConnectedUser= '2AB0D13C-2BBE-46F5-990D-533067BE2EB3';

然后你可以在强类型化的同时初始化你的对象。

    const parameters: PaymentRequestDto = {
        PaymentSource,
        PaymentCenterUid: PaymentCenter,
        ConnectedUserUid: ConnectedUser,
    };

PaymentSource 不需要名称字段说明符,因为使用的变量与该字段具有相同的名称。

这也适用于数组。

    const parameters: PaymentRequestDto [] = [
      {
        PaymentSource,
        PaymentCenterUid: PaymentCenter,
        ConnectedUserUid: ConnectedUser,
      },
      {
      . . . .
      }
    ];
于 2021-04-22T20:37:37.230 回答
-2

如果您想创建新实例而不在实例时设置初始值

1-你必须使用类而不是接口

2-创建类时必须设置初始值

export class IStudentDTO {
 Id:        number = 0;
 Name:      string = '';


student: IStudentDTO = new IStudentDTO();
于 2019-12-03T12:33:27.357 回答