TypeScriptとフィールド初期化子


251

そのような方法で新しいクラスを初期化するTS方法(C#私が欲しいものを示すための例):

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

[編集]
この質問を書いているとき、JSの知識がほとんどない純粋な.NET開発者でした。TypeScriptもまったく新しいもので、JavaScriptの新しいC#ベースのスーパーセットとして発表されました。今日、私はこの質問がどれほど愚かだったかを知りました。

とにかく、まだ誰かが答えを探しているなら、下の可能な解決策を見てください。

最初に注意すべきことは、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 2015

1
@JacobFosheeは有効なJavaScriptではありませんか?私のクロム開発ツールを参照してください。i.imgur.com/vpathu6.png (ただし、Visual Studioのコードまたは必然的に文句を言うでしょうLINTED他の活字体)
— 火星ロバートソン

5
@MichalStefanow OPがコメントを投稿した後、OPが質問を編集しました。彼は持っていたreturn new MyClass { Field1: "ASD", Field2: "QWE" };
— ジェイコブ・フォシー

回答:


90

更新

この答えを書いて以来、より良い方法が思い付きました。投票数が多く、より良い回答がある以下の他の回答をご覧ください。この回答は承認済みとマークされているため、削除できません。


古い答え

これを説明するTypeScript codeplexに問題があります:オブジェクト初期化子のサポート。

すでに述べたように、クラスの代わりに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#の例と同等かどうかはわかりません。
— ジャックウェスター2013

3
インターフェース名は大文字の「I」から始める方がよい
— Kiarash

16
1. Personはクラスではなく、2。@JackWesterは正しい、bobはPersonのインスタンスではありません。alert(bob instanceof Person);を試してください。このコード例では、タイプアサーションの目的でのみPersonが存在しています。
— ジャック

15
私はジャックとジャックに同意し、繰り返す価値があると思います。あなたのボブはタイプ Personですが、のインスタンスではありませんPerson。Person実際に複雑なコンストラクタと多数のメソッドを持つクラスであると想像してみてください。このアプローチは面倒です。多くの人々があなたのアプローチが有用だと思ったのは良いことですが、それは述べられているように質問の解決策ではありませんし、インターフェースの代わりに例でクラスPersonを使用することもできます。同じタイプです。
— Alex

26
明らかに間違っているのに、なぜこれが受け入れられた答えなのですか?
— Hendrik Wiese 2017年

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#スタイルとほぼ同じです(実際のfield-init構文は拒否されました)。私は適切なフィールド初期化子をはるかに好みますが、それがまだ起こるようには見えません。

比較のために、キャストアプローチを使用する場合、初期化オブジェクトには、キャスト先の型のすべてのフィールドが必要です。さらに、クラス自体によって作成されたクラス固有の関数(または派生)を取得しないでください。


1
+1。これにより、実際にはクラスのインスタンスが作成され(これらのソリューションのほとんどでは作成されません)、コンストラクター内のすべての機能が維持され(Object.createや外部のその他の残骸はありません)、パラメーターの順序に依存する代わりにプロパティ名が明示的に指定されます(ここに私の個人的な好み)。ただし、パラメーターとプロパティの間の型/コンパイルの安全性は失われます。
— フィドル

1
@ user1817787クラス自体にデフォルトのオプションがあるものは何でも定義した方がよいでしょうが、デフォルトを割り当てます。次にPartial<>、を使用しないでPersonください。必要なフィールドを持つオブジェクトを渡す必要があります。とはいえ、タイプマッピングを特定のフィールドに制限するアイデア(ピックを参照)については、こちらを参照してください。
— Meirion Hughes

2
これが本当に答えになるはずです。この問題に対するその最良の解決策。
— Ascherer 2017

9
それはゴールデンのコードですpublic constructor(init?:Partial<Person>) { Object.assign(this, init); }
— kuncevic.dev '25 / 07/25

3
Object.assignが私と同じようなエラーを表示している場合は、次のSO回答を参照してください:stackoverflow.com/a/38860354/2621693
— Jim 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() );


これも同様に、JavaScriptオブジェクトからユーザーオブジェクトを作成できます
— Dave Keane '30

1
6年半後、私はこれを忘れましたが、それでも好きです。再度、感謝します。
— PRMan、

25

クラス型にキャストされた匿名オブジェクトに影響を与えることができます。 ボーナス:ビジュアルスタジオでは、このようにインテリセンスのメリットがあります:)

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

編集:

警告クラスにメソッドがある場合、クラスのインスタンスはそれらを取得しません。AClassにコンストラクタがある場合、それは実行されません。instanceof AClassを使用すると、falseになります。

結論として、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にコンストラクタがある場合は、実行されません。
— マイケルエリクソン

1
また、実行anInstance instanceof AClassするとfalse、実行時に取得されます。
— Lucero、2018年

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 2017

また、条件付きタイプマッピングを取得すると、関数のみをパーシャルにマップし、プロパティをそのまま保持できます。:)
— Meirion Hughes 2017

代わりに、洗練されたのclass後、var私がしなければ、var dog: {name: string} = {name: 'will be assigned later'};それはコンパイルした作品。不足や問題はありますか?ああ、dogスコープが非常に小さく、具体的です。つまり、1つのインスタンスのみです。
— Jeb50

11

私は、(オプションで)自動プロパティとデフォルトを使用して、この方法でそれを行う傾向があります。2つのフィールドがデータ構造の一部であることを示唆していないため、この方法を選択しました。

クラスにプロパティを設定して、通常の方法で割り当てることができます。そして、明らかにそれらは必要かもしれないし必要でないかもしれないので、それもまた別のことです。これは、このような構文上の砂糖です。

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 2013年

+1コンストラクターは、可能な場合に進む方法です。しかし、多くのフィールドがあり、それらの一部のみを初期化したい場合は、私の答えが物事をより簡単にすると思います。
— Meirion Hughes

1
多くのフィールドがある場合、コンストラクターに名前のない値の壁を渡すので、このようなオブジェクトの初期化はかなり扱いにくいでしょう。この方法にメリットがないと言っているのではありません。フィールドの少ない単純なオブジェクトに最適です。ほとんどの出版物は、メンバーの署名の4つまたは5つのパラメーターが上限であると述べています。TSイニシャライザを検索しているときに誰かのブログでこのソリューションへのリンクを見つけたので、これを指摘するだけです。単体テストのために初期化する必要がある20以上のフィールドを持つオブジェクトがあります。
— ダスティンクリーブランド

11

シナリオによっては、使用しても問題ない場合がありますObject.create。Mozillaリファレンスには、後方互換性が必要な場合、または独自の初期化関数をロールしたい場合に、ポリフィルが含まれています。

あなたの例に適用:

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

便利なシナリオ

  • ユニットテスト
  • インライン宣言

私の場合、2つの理由で単体テストでこれが便利であることがわかりました。

  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+でもっとうまくできるか
— どうか疑問に思う

1
私の知る限り、これが今日の最善の方法です。ありがとうございました。
— フロリアンノルベルトベプンクト

このソリューションには1つの問題があります。@ VitalyB:メソッドにパラメーターがあるとすぐに、これは壊れます。getFullName(){return "bar"}は機能しますが、getFullName(str:string):string {return str}は機能しません
— フロリアンノルベルトベプンクト

@floriannorbertbepunkt何があなたのために働いていないのですか?それは私にとってはうまく機能しているようです...
— rfgamaral


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

問題は、すべてのフィールドがオプションになり、ほとんどの場合望ましくないことです。


1

これを行う最も簡単な方法は、型キャストです。

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

7
残念ながら、(1)これは、キャストを入力されていませんが、コンパイル時タイプ- アサーション、(2)質問は尋ねた「新しいinitにどのクラス」(強調鉱山)を、そしてこのアプローチはそれを達成するために失敗します。TypeScriptにこの機能があればいいのですが、残念ながらそうではありません。
— John Weisz 2017年

の定義は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.