TypeScript 및 필드 이니셜 라이저


252

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

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

[편집]
이 질문을 쓸 때 JS 지식이 전혀없는 순수한 .NET 개발자였습니다. 또한 TypeScript는 완전히 새로운 것으로 C # 기반의 새로운 JavaScript 수퍼 셋으로 발표되었습니다. 오늘 나는이 질문이 얼마나 어리석은지를 봅니다.

어쨌든 누군가가 여전히 답을 찾고 있다면 아래 가능한 해결책을보십시오.

가장 먼저 주목해야 할 것은 TS에서 모델을위한 빈 클래스를 생성해서는 안된다는 것입니다. 더 나은 방법은 인터페이스 또는 유형을 만드는 것입니다 (필요에 따라 다름). Todd Motto의 좋은 기사 : https://ultimatecourses.com/blog/classes-vs-interfaces-in-typescript

해결 방법 1 :

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

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

해결책 2 :

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

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

솔루션 3 (실제로 수업이 필요할 때) :

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

또는

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

물론 두 경우 모두 함수 / 메소드 반환 유형에서 해결되므로 캐스팅 유형을 수동으로 필요로하지 않을 수 있습니다.


9
질문에 방금 추가 한 "솔루션"은 유효한 TypeScript 또는 JavaScript가 아닙니다. 그러나 시도하는 것이 가장 직관적이라는 것을 지적하는 것이 중요합니다.
Jacob Foshee

1
@JacobFoshee가 유효하지 않은 JavaScript입니까? 내 크롬 개발 도구를 참조하십시오 i.imgur.com/vpathu6.png (하지만 비주얼 스튜디오 코드 또는 필연적 불평 것 linted 다른 타이프)
화성 로버트슨

5
@MichalStefanow OP는 그 의견을 게시 한 후 질문을 편집했습니다. 그는return new MyClass { Field1: "ASD", Field2: "QWE" };
Jacob Foshee

답변:


90

최신 정보

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


이전 답변

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

81
귀하의 예에서 bob은 Person 클래스의 인스턴스가 아닙니다. 이것이 C # 예제와 어떻게 같은지 알 수 없습니다.
Jack Wester

3
인터페이스 이름은 대문자 "I"로 시작하는 것이 좋습니다
Kiarash

16
1. Person은 수업이 아니며 2. @JackWester는 옳습니다. bob은 Person의 인스턴스가 아닙니다. 경고를 시도하십시오 (bob instanceof Person); 이 코드 예제에서 Person은 Type Assertion 목적으로 만 사용됩니다.
Jacques

15
나는 Jack and Jaques에 동의하며 반복 할 가치가 있다고 생각합니다. 당신의 밥은 유형 Person 이지만, 전혀 인스턴스가 아닙니다 Person. Person실제로 복잡한 생성자와 많은 메소드가있는 클래스 라고 상상해보십시오 .이 접근법은 얼굴에 평평 해집니다. 많은 사람들이 귀하의 접근 방식이 유용하다는 것을 알았지 만 설명 된 질문에 대한 해결책은 아니며 인터페이스 대신 Person 클래스를 사용할 수도 있습니다. 동일한 유형입니다.
Alex

26
명확하지 않은 경우 왜 이것이 허용되는 대답입니까?
Hendrik Wiese

447

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

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


1
+1. 이것은 실제로 클래스의 인스턴스를 생성하고 (대부분의 솔루션은 그렇지 않습니다) 생성자 내부의 모든 기능을 유지합니다 (Object.create 또는 기타 cruft 없음). 매개 변수 순서에 의존하는 대신 속성 이름을 명시 적으로 나타냅니다 ( 내 개인적인 취향). 그러나 매개 변수와 속성 사이의 형식 / 컴파일 안전이 손실됩니다.
바이올린

1
@ user1817787 클래스 자체에서 기본값이 선택 사항이지만 기본값을 지정하는 것은 더 나은 방법 일 것입니다. 그런 다음 사용하지 않는 Partial<>그냥 Person필수 필드를 가진 개체를 전달할 수 그게 당신이 필요합니다 -. 즉, 특정 필드로 유형 매핑을 제한하는 아이디어 (선택 참조)는 여기참조하십시오 .
Meirion Hughes

2
이것이 실제로 답이되어야합니다. 이 문제에 가장 적합한 솔루션입니다.
Ascherer

9
그것은 황금 조각입니다public constructor(init?:Partial<Person>) { Object.assign(this, init); }
kuncevic.dev

3
Object.assign가 나를 위해처럼 오류가 표시되는 경우,이 SO의 대답을 참조하십시오 stackoverflow.com/a/38860354/2621693
짐 Yarbro

27

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

작업 예

이 예제는 대략적인 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() );


이것과 마찬가지로 자바 스크립트 객체로부터 사용자 객체를 만들 수 있습니다
Dave Keane

1
6½ 년 후 나는 이것을 잊었다. 그러나 나는 아직도 그것을 좋아한다. 다시 감사합니다.
PRMan

25

클래스 유형으로 캐스팅 된 익명 객체에 영향을 줄 수 있습니다. 보너스 : Visual Studio에서 다음과 같은 방법으로 인텔리전스를 얻습니다. :)

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

편집하다:

경고 클래스에 메서드가 있으면 클래스의 인스턴스가 메서드를 가져 오지 못합니다. AClass에 생성자가 있으면 실행되지 않습니다. instanceof AClass를 사용하면 거짓이됩니다.

결론적으로 class가 아닌 interface를 사용해야합니다 . 가장 일반적인 용도는 Plain Old Objects로 선언 된 도메인 모델입니다. 실제로 도메인 모델의 경우 클래스 대신 인터페이스를 사용하는 것이 좋습니다. 인터페이스는 타입 검사를 위해 컴파일 타임에 사용되며 클래스와 달리 컴파일 중에 인터페이스가 완전히 제거됩니다.

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

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

20
경우 AClass포함 방법 anInstance을 얻을 것입니다.
Monsignor

2
또한 AClass에 생성자가 있으면 실행되지 않습니다.
Michael Erickson

1
또한 당신이 런타임에 anInstance instanceof AClass얻을 것이다 false.
Lucero

15

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>하지 않음
  • 기능을 지원합니다 (기능을 지원하지 않는 단순 유형 명제와 비교)

1
그것은 필수 분야를 존중하지 않습니다 : new Person(<Person>{});(주조 때문에) 또한 명확합니다. Partial <T> 사용은 기능을 지원합니다. 궁극적으로 필요한 필드 (및 프로토 타입 기능)가 필요한 경우 다음을 수행해야합니다 init: { name: string, address?: string, age: number }.
Meirion Hughes

또한 조건부 유형 매핑 을 받으면 함수 만 부분적으로 매핑하고 속성을 그대로 유지할 수 있습니다. :)
Meirion Hughes

대신 정교한의 class다음 var내가 할 경우 var dog: {name: string} = {name: 'will be assigned later'};, 그것은 컴파일하고 작동합니다. 결함이나 문제가 있습니까? 아, dog범위가 매우 작고 구체적이므로 하나의 인스턴스 만 의미합니다.
Jeb50

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
나의 가장 깊은 사과. 그러나 실제로 "필드 이니셜 라이저에 대해 묻지"않았으므로 TS에서 클래스를 새로 만드는 대체 방법에 관심이 있다고 가정하는 것이 당연합니다. 공감할 준비가 되었다면 질문에 조금 더 많은 정보를 줄 수 있습니다.
Ralph Lavelle

+1 생성자는 가능한 곳으로가는 길입니다. 그러나 많은 필드가 있고 일부만 초기화하려는 경우 내 대답이 일을 더 쉽게 만든다고 생각합니다.
Meirion Hughes

1
많은 필드가있는 경우 생성자에게 이름없는 값의 벽을 전달할 때 이와 같은 객체를 초기화하는 것은 다루기 어려울 것입니다. 이 방법이 장점이 없다고 말하는 것은 아닙니다. 필드가 적은 간단한 객체에 가장 적합합니다. 대부분의 간행물은 회원의 서명에있는 약 4-5 개의 매개 변수가 상한이라고 말합니다. TS 초기화 프로그램을 검색하는 동안 누군가의 블로그 에서이 솔루션에 대한 링크를 찾았 기 때문에 이것을 지적했습니다. 단위 테스트를 위해 초기화 해야하는 20 개 이상의 필드가있는 객체가 있습니다.
더스틴 클리블랜드

11

일부 시나리오에서는 사용이 허용 될 수 있습니다 Object.create . 역 호환성이 필요하거나 고유 한 이니셜 라이저 함수를 롤링하려는 경우 Mozilla 참조에 폴리 필이 포함됩니다.

귀하의 예에 적용 :

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

9

다음과 같은 솔루션을 원했습니다.

  • 모든 데이터 객체가 필요하며 생성자가 채워야합니다.
  • 기본값을 제공 할 필요가 없습니다.
  • 클래스 내에서 함수를 사용할 수 있습니다.

내가하는 방법은 다음과 같습니다.

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 필요
  • 게터 / 세터가있는 클래스

이 대답은 나에게 가장 이상적으로 보입니다. 우리가 typescript 3+로 더 잘할 수 있을지 궁금해
RyanM

1
내가 알 수있는 한, 이것은 오늘날 가장 좋은 방법입니다. 감사합니다.
Florian Norbert Bepunkt

방법은 매개 변수가 자마자,이 휴식 시간 : getFullName () {반환 "바"} 작동하지만, getFullName이 솔루션 하나의 문제가 있습니다, @VitalyB (STR : 문자열) : 문자열 {반환 STR}이 작동하지 않습니다
Florian Norbert Bepunkt

@floriannorbertbepunkt 정확히 작동하지 않는 것은 무엇입니까? 그것은 나를 위해 잘 작동하는 것 같습니다 ...
rfgamaral

3

이것은 또 다른 해결책입니다.

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

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

이 경우 필수 필드를 생략 할 수 없습니다. 이를 통해 객체 구성을 세밀하게 제어 할 수 있습니다.

다른 답변에서 언급했듯이 생성자를 부분 유형과 함께 사용할 수 있습니다.

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

문제는 모든 필드가 선택 사항이되고 대부분의 경우 바람직하지 않다는 것입니다.


1

가장 쉬운 방법은 타입 캐스팅입니다.

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

7
불행히도 (1) ​​이것은 유형 캐스팅이 아니라 컴파일 타임 유형 주장입니다 . (2) 질문은 "새 클래스 를 초기화하는 방법 " (강조 광산)에 대한 질문 이었습니다. TypeScript 에이 기능이 있다면 분명히 좋을 것입니다. 그러나 불행히도 그렇지 않습니다.
John Weisz

의 정의는 어디에 MyClass있습니까?
Jeb50

0

이전 버전의 typescript <2.1을 사용하는 경우 기본적으로 유형이 지정된 객체로 캐스팅하는 다음과 유사하게 사용할 수 있습니다.

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

참고 :이 방법을 사용하면 개체의 모든 방법이 제거되므로 데이터 모델에만 적합합니다. 기본적으로 모든 객체를 유형이 지정된 객체로 캐스팅합니다.


-1

인스턴스시 초기 값을 설정하지 않고 새 인스턴스를 작성하려는 경우

1- 인터페이스가 아닌 클래스를 사용해야합니다

2- 당신은 클래스를 만들 때 초기 값을 설정해야합니다

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


student: IStudentDTO = new IStudentDTO();
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.