TypeScript 和字段初始值设定项

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14142071/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-11 00:58:42  来源:igfitidea点击:

TypeScript and field initializers

typescript

提问by Nickon

How to init a new class in TSin such a way (example in C#to show what I want):

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

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

[edit]
When I was writing this question I was pure .NET developer without much of JS knowledge. Also TypeScript was something completely new, announced as new C#-based superset of JavaScript. Today I see how stupid this question was.

[编辑]
当我写这个问题时,我是纯 .NET 开发人员,没有太多的 JS 知识。此外,TypeScript 是一种全新的东西,作为新的基于 C# 的 JavaScript 超集宣布。今天我看到这个问题是多么愚蠢。

Anyway if anyone still is looking for an answer, please, look at the possible solutions below.

无论如何,如果有人仍在寻找答案,请查看以下可能的解决方案。

First thing to note is in TS we shouldn't create empty classes for models. Better way is to create interface or type (depending on needs). Good article from Todd Motto: https://ultimatecourses.com/blog/classes-vs-interfaces-in-typescript

首先要注意的是在 TS 中我们不应该为模型创建空类。更好的方法是创建接口或类型(取决于需要)。托德格言的好文章:https: //ultimatecourses.com/blog/classes-vs-interfaces-in-typescript

SOLUTION 1:

解决方案1:

type MyType = { prop1: string, prop2: string };

return <MyType> { prop1: '', prop2: '' };

SOLUTION 2:

解决方案2:

type MyType = { prop1: string, prop2: string };

return { prop1: '', prop2: '' } as MyType;

SOLUTION 3 (when you really need a class):

解决方案 3(当你真的需要一个类时):

class MyClass {
   constructor(public data: { prop1: string, prop2: string }) {}
}
// ...
return new MyClass({ prop1: '', prop2: '' });

or

或者

class MyClass {
   constructor(public prop1: string, public prop2: string) {}
}
// ...
return new MyClass('', '');

Of course in both cases you may not need casting types manually because they will be resolved from function/method return type.

当然,在这两种情况下,您可能不需要手动转换类型,因为它们将从函数/方法返回类型中解析。

采纳答案by Wouter de Kort

Update

更新

Since writing this answer, better ways have come up. Please see the other answers below that have more votes and a better answer. I cannot remove this answer since it's marked as accepted.

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



Old answer

旧答案

There is an issue on the TypeScript codeplex that describes this: Support for object initializers.

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

As stated, you can already do this by using interfaces in TypeScript instead of classes:

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

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

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

回答by Meirion Hughes

Updated 07/12/2016:Typescript 2.1 introduces Mapped Typesand provides Partial<T>, which allows you to do this....

2016 年 7 月 12 日更新:Typescript 2.1 引入了映射类型并提供了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"}),
];

Original Answer:

原答案:

My approach is to define a separate fieldsvariable that you pass to the constructor. The trick is to redefine all the class fields for this initialiser as optional. When the object is created (with its defaults) you simply assign the initialiser object onto this;

我的方法是定义一个单独的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);
    }
}

or do it manually (bit more safe):

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

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

usage:

用法:

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
]; 

and console output:

和控制台输出:

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 }   

This gives you basic safety and property initialization, but its all optional and can be out-of-order. You get the class's defaults left alone if you don't pass a field.

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

You can also mix it with required constructor parameters too -- stick fieldson the end.

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

About as close to C# style as you're going to get I think (actual field-init syntax was rejected). I'd much prefer proper field initialiser, but doesn't look like it will happen yet.

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

For comparison, If you use the casting approach, your initialiser object must have ALL the fields for the type you are casting to, plus don't get any class specific functions (or derivations) created by the class itself.

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

回答by Jonathan B.

Below is a solution that combines a shorter application of Object.assignto more closely model the original C#pattern.

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

But first, lets review the techniques offered so far, which include:

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

  1. Copy constructors that accept an object and apply that to Object.assign
  2. A clever Partial<T>trick within the copy constructor
  3. Use of "casting" against a POJO
  4. Leveraging Object.createinstead of Object.assign
  1. 接受对象并将其应用到的复制构造函数 Object.assign
  2. Partial<T>复制构造函数中的一个巧妙技巧
  3. 对 POJO 使用“强制转换”
  4. 利用Object.create而不是Object.assign

Of course, each have their pros/cons. Modifying a target class to create a copy constructor may not always be an option. And "casting" loses any functions associated with the target type. Object.createseems less appealing since it requires a rather verbose property descriptor map.

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

Shortest, General-Purpose Answer

最短的通用答案

So, here's yet another approach that is somewhat simpler, maintains the type definition and associated function prototypes, and more closely models the intended C#pattern:

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

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

That's it. The only addition over the C#pattern is Object.assignalong with 2 parenthesis and a comma. Check out the working example below to confirm it maintains the type's function prototypes. No constructors required, and no clever tricks.

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

Working Example

工作示例

This example shows how to initialize an object using an approximation of a C#field initializer:

此示例显示如何使用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() );

回答by rdhainaut

You can affect an anonymous object casted in your class type. Bonus: In visual studio, you benefit of intellisense this way :)

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

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

Edit:

编辑:

WARNINGIf the class has methods, the instance of your class will not get them. If AClass has a constructor, it will not be executed. If you use instanceof AClass, you will get false.

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

In conclusion, you should used interface and not class. The most common use is for the domain model declared as Plain Old Objects. Indeed, for domain model you should better use interface instead of class. Interfaces are use at compilation time for type checking and unlike classes, interfaces are completely removed during compilation.

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

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

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

回答by VeganHunter

I suggest an approach that does not require Typescript 2.1:

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

key points:

关键点:

  • Typescript 2.1 not required, Partial<T>not required
  • It supports functions (in comparison with simple type assertion which does not support functions)
  • 不需要打字稿 2.1,Partial<T>不需要
  • 它支持函数(与不支持函数的简单类型断言相比)

回答by Ralph Lavelle

I'd be more inclined to do it this way, using (optionally) automatic properties and defaults. You haven't suggested that the two fields are part of a data structure, so that's why I chose this way.

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

You could have the properties on the class and then assign them the usual way. And obviously they may or may not be required, so that's something else too. It's just that this is such nice syntactic sugar.

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

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

回答by Jacob Foshee

In some scenarios it may be acceptable to use Object.create. The Mozilla reference includes a polyfill if you need back-compatibility or want to roll your own initializer function.

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

Applied to your example:

应用于您的示例:

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

Useful Scenarios

有用的场景

  • Unit Tests
  • Inline declaration
  • 单元测试
  • 内联声明

In my case I found this useful in unit tests for two reasons:

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

  1. When testing expectations I often want to create a slim object as an expectation
  2. Unit test frameworks (like Jasmine) may compare the object prototype (__proto__) and fail the test. For example:
  1. 在测试期望时,我经常想创建一个细长的对象作为期望
  2. 单元测试框架(如 Jasmine)可能会比较对象原型 ( __proto__) 并导致测试失败。例如:
var actual = new MyClass();
actual.field1 = "ASD";
expect({ field1: "ASD" }).toEqual(actual); // fails

The output of the unit test failure will not yield a clue about what is mismatched.

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

  1. In unit tests I can be selective about what browsers I support
  1. 在单元测试中,我可以选择我支持的浏览器

Finally, the solution proposed at http://typescript.codeplex.com/workitem/334does not support inline json-style declaration. For example, the following does not compile:

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

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

回答by VitalyB

I wanted a solution that would have the following:

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

  • All the data objects are required and must be filled by the constructor.
  • No need to provide defaults.
  • Can use functions inside the class.
  • 所有数据对象都是必需的,必须由构造函数填充。
  • 无需提供默认值。
  • 可以在类内部使用函数。

Here is the way that I do it:

这是我这样做的方式:

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

All the properties in the constructor are mandatory and may not be omitted without a compiler error.

构造函数中的所有属性都是必需的,并且在没有编译器错误的情况下不能省略。

It is dependant on the OnlyDatathat filters out getFullName()out of the required properties and it is defined like so:

它取决于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>;

Current limitations of this way:

这种方式的当前限制:

  • Requires TypeScript 2.8
  • Classes with getters/setters
  • 需要打字稿 2.8
  • 带有 getter/setter 的类

回答by rostamiani

This is another solution:

这是另一种解决方案:

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

回答by Alex

You could have a class with optional fields (marked with ?) and a constructor that receives an instance of the same class.

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

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" }),
];

In this case, you will not be able to omit the required fields. This gives you fine-grained control over the object construction.

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

You could use the constructor with the Partial type as noted in other answers:

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

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

The problem is that all fields become optional and it is not desirable in most cases.

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