TypeScript和字段初始值设定项

时间:2013-01-03 15:36:45

标签: typescript

如何以这种方式在TS中初始化新课程(例如在C#中显示我想要的内容):

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

SOLUTION:
经典JavaScript语法:

return { Field1: "ASD", Field2: "QWE" };

17 个答案:

答案 0 :(得分:313)

2016年7月12日更新: Typescript 2.1 introduces 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#风格,我认为(actual field-init syntax was rejected)。我更喜欢适当的字段初始化,但看起来还不会发生。

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

答案 1 :(得分:64)

TypeScript codeplex上存在一个问题:Support for object initializers

如上所述,您可以通过在TypeScript中使用接口而不是类来执行此操作:

interface Name {
    first: string;
    last: string;
}
class Person {
    name: Name;
    age: number;
}

var bob: Person = {
    name: {
        first: "Bob",
        last: "Smith",
    },
    age: 35,
};

答案 2 :(得分:15)

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

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

编辑:

警告如果类有方法,则类的实例将无法获取它们。如果AClass有一个构造函数,它将不会被执行。如果您使用instanceof AClass,您将得到错误。

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

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

var anObject: IModel = {
     Property1: "Value",
     Property2: "Value",
     PropertyBoolean: true,
     PropertyNumber: 1
 };

答案 3 :(得分:14)

以下是一种解决方案,它结合了Object.assign的较短应用,以更接近地模拟原始C#模式。

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

  1. 复制接受对象并将其应用于Object.assign
  2. 的构造函数
  3. 复制构造函数中的一个聪明的Partial<T>技巧
  4. 使用&#34;铸造&#34;反对POJO
  5. 利用Object.create代替Object.assign
  6. 当然,每个人都有自己的优缺点。修改目标类以创建复制构造函数可能并不总是一个选项。并且&#34;铸造&#34;丢失与目标类型相关的任何功能。 Object.create似乎不太吸引人,因为它需要一个相当冗长的属性描述符映射。

    最短的通用答案

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

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

    那就是它。 C#模式的唯一添加是Object.assign以及2个括号和逗号。查看下面的工作示例,确认它保留了类型的函数原型。不需要构造函数,也没有聪明的技巧。

    工作示例

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

    &#13;
    &#13;
    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() );
    &#13;
    &#13;
    &#13;

答案 4 :(得分:12)

我建议一种不需要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();

关键点:

  • 不需要打字稿2.1,不需要Partial<T>
  • 它支持函数(与不支持函数的简单类型断言相比)

答案 5 :(得分:11)

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

您可以拥有该类的属性,然后以通常的方式分配它们。显然它们可能需要也可能不需要,所以这也是其他的东西。只是这是一个很好的语法糖。

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

答案 6 :(得分:10)

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

应用于您的示例:

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

有用的方案

  • 单元测试
  • 内联声明

在我的情况下,我发现这在单元测试中很有用,原因有两个:

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

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

    1. 在单元测试中,我可以选择支持哪些浏览器
    2. 最后,http://typescript.codeplex.com/workitem/334提出的解决方案不支持内联json样式声明。例如,以下内容无法编译:

      var o = { 
        m: MyClass: { Field1:"ASD" }
      };
      

答案 7 :(得分:2)

我想要一个具有以下优点的解决方案:

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

这是我的操作方式:

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的类

答案 8 :(得分:2)

您可能有一个带有可选字段的类(标有?),并且有一个接收相同类实例的构造函数。

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);
}

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

答案 9 :(得分:2)

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

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)
  }
}

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

答案 10 :(得分:1)

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

return <MyClass>{ Field1: "ASD", Field2: "QWE" };

答案 11 :(得分: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;

答案 12 :(得分:0)

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

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

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# 对象初始化的感觉。

答案 13 :(得分: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,
      },
      {
      . . . .
      }
    ];

答案 14 :(得分:0)

这是另一种解决方案:

return {
  Field1 : "ASD",
  Field2 : "QWE" 
} as myClass;

答案 15 :(得分:0)

如果要创建实例时不设置初始值的新实例

1-您必须使用类而非接口

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

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


student: IStudentDTO = new IStudentDTO();

答案 16 :(得分:-1)

如果您使用旧版本的打字稿&lt; 2.1然后你可以使用类似于以下内容,基本上是任何类型对象的转换:

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

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