Programing

TypeScript 및 필드 이니셜 라이저

lottogame 2020. 5. 17. 10:30
반응형

TypeScript 및 필드 이니셜 라이저


TS그런 식으로 새 수업을 시작하는 방법 (예 C#: 내가 원하는 것을 보여주기 위해) :

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

솔루션 :
기본 JavaScript구문 :

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

최신 정보

이 답변을 쓴 이후로 더 좋은 방법이 생겼습니다. 더 많은 투표와 더 나은 답변이있는 아래의 다른 답변을 참조하십시오. 이 답변은 허용 된 것으로 표시되어 있으므로 제거 할 수 없습니다.


이전 답변

TypeScript codeplex에 이것을 설명하는 문제가 있습니다 : 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,
};

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

원래 답변 :

내 접근 방식은 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 # 스타일에 가깝습니다 ( 실제 필드 초기화 구문이 거부되었습니다 ). 적절한 필드 이니셜 라이저를 선호하지만 아직 일어날 것 같지는 않습니다.

비교를 위해, 캐스팅 방식을 사용하는 경우 이니셜 라이저 객체에는 캐스팅하려는 유형의 모든 필드가 있어야하며 클래스 자체에서 생성 한 클래스 별 함수 (또는 파생)가 없어야합니다.


아래는 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.assign괄호 2 개와 쉼표 2 개 뿐입니다 . 타입의 함수 프로토 타입을 유지하는지 확인하기 위해 아래 작업 예제를 확인하십시오. 생성자가 필요없고 영리한 트릭도 없습니다.

작업 예

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

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


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:

WARNING If 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.

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 IModel {
   Property1: string;
   Property2: string;
   PropertyBoolean: boolean;
   PropertyNumber: number;
}

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

I suggest an approach that does not require 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)

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.

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

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

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

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

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 OnlyData that filters out getFullName() out of the required properties and it is defined like so:

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

The easiest way to do this is with type casting.

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

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

참고URL : https://stackoverflow.com/questions/14142071/typescript-and-field-initializers

반응형