TypeScript and field initializers

前端 未结 14 2251
暖寄归人
暖寄归人 2020-11-30 16:49

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

// ... some code before
return new MyClass { Field         


        
相关标签:
14条回答
  • 2020-11-30 17:22

    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:

    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.

    0 讨论(0)
  • 2020-11-30 17:24

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

    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 fields variable 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;

    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 fields on the end.

    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.

    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.

    0 讨论(0)
  • 2020-11-30 17:27

    If you're using an old version of typescript < 2.1 then you can use similar to the following which is basically casting of any to typed object:

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

    NOTE: Using this method is only good for data models as it will remove all the methods in the object. It's basically casting any object to a typed object

    0 讨论(0)
  • 2020-11-30 17:31

    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.

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

    interface Name {
        first: string;
        last: string;
    }
    class Person {
        name: Name;
        age: number;
    }
    
    var bob: Person = {
        name: {
            first: "Bob",
            last: "Smith",
        },
        age: 35,
    };
    
    0 讨论(0)
  • 2020-11-30 17:31

    To init a class without redeclaring all the properties for defaults:

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

    This combines some of the already excellent answers

    0 讨论(0)
  • 2020-11-30 17:32

    This is another solution:

    return {
      Field1 : "ASD",
      Field2 : "QWE" 
    } as myClass;
    
    0 讨论(0)
提交回复
热议问题