web-dev-qa-db-ja.com

TypeScriptのプロパティを使用して関数オブジェクトを作成する

また、いくつかのプロパティを保持する関数オブジェクトを作成します。たとえば、JavaScriptでは次のようにします。

var f = function() { }
f.someValue = 3;

TypeScriptで、このタイプを次のように説明できます。

var f: { (): any; someValue: number; };

ただし、キャストを必要とせずに実際にビルドすることはできません。といった:

var f: { (): any; someValue: number; } =
    <{ (): any; someValue: number; }>(
        function() { }
    );
f.someValue = 3;

キャストなしでこれをどのように構築しますか?

63
JL235

したがって、キャストせずに単純にその関数を「f」に構築して割り当てることが要件である場合、考えられる解決策は次のとおりです。

var f: { (): any; someValue: number; };

f = (() => {
    var _f : any = function () { };
    _f.someValue = 3;
    return _f;
})();

基本的に、自己実行関数リテラルを使用して、割り当てが完了する前にその署名に一致するオブジェクトを「構築」します。唯一の奇妙な点は、関数の内部宣言が 'any'型である必要があることです。それ以外の場合、コンパイラは、オブジェクトにまだ存在しないプロパティに割り当てていることを叫びます。

編集:コードを少し簡素化しました。

18
nxn

更新:この回答はTypeScriptの以前のバージョンでは最適なソリューションでしたが、新しいバージョンではより優れたオプションが利用可能です(他の回答を参照)。

受け入れられた答えは機能し、状況によっては必要になる場合がありますが、オブジェクトを構築するための型の安全性を提供しないという欠点があります。未定義のプロパティを追加しようとすると、この手法は少なくとも型エラーをスローします。

interface F { (): any; someValue: number; }

var f = <F>function () { }
f.someValue = 3

// type error
f.notDeclard = 3
96
Greg Weber

これはObject.assign(target, source)で簡単に達成できます(TypeScript 2.x)

例:

enter image description here

ここでの魔法は、Object.assign<T, U>(t: T, u: U)intersectionT & U

これが既知のインターフェイスに解決されることを強制することも簡単です。例えば:

interface Foo {
  (a: number, b: string): string[];
  foo: string;
}

let method: Foo = Object.assign(
  (a: number, b: string) => { return a * a; },
  { foo: 10 }
); 

互換性のない入力によるエラー:

エラー:foo:numberはfoo:stringに割り当てられません
エラー:数値をstring []に割り当てることができません(戻り型)

注意:古いブラウザをターゲットにしている場合は polyfill Object.assignが必要な場合があります。

53
Meirion Hughes

TypeScriptは、このケースを 宣言マージ で処理するように設計されています:

また、関数を作成し、関数にプロパティを追加して関数をさらに拡張するJavaScriptの慣習に精通しているかもしれません。 TypeScriptは宣言マージを使用して、このような定義をタイプセーフな方法で構築します。

宣言のマージにより、何かが関数と名前空間(内部モジュール)の両方であると言えます。

_function f() { }
namespace f {
    export var someValue = 3;
}
_

これにより、入力が保持され、f()と_f.someValue_の両方を記述できます。既存のJavaScriptコードの_.d.ts_ファイルを作成するときは、declareを使用します。

_declare function f(): void;
declare namespace f {
    export var someValue: number;
}
_

関数へのプロパティの追加は、TypeScriptで混乱したり、予期しないパターンになることが多いため、避けるようにしてください。ただし、古いJSコードを使用または変換するときに必要になる場合があります。これは、内部モジュール(名前空間)と外部モジュールを混在させることが適切な場合の1つです。

41
mk.

ショートカットとして、['property']アクセサーを使用してオブジェクト値を動的に割り当てることができます。

var f = function() { }
f['someValue'] = 3;

これにより、型チェックがバイパスされます。ただし、同じ方法で意図的にプロパティにアクセスする必要があるため、かなり安全です。

var val = f.someValue; // This won't work
var val = f['someValue']; // Yeah, I meant to do that

ただし、プロパティ値の型チェックが本当に必要な場合、これは機能しません。

2
Rick Love

更新された回答:&を介した交差タイプの追加により、2つの推論されたタイプをその場で「マージ」することが可能です。

これは、いくつかのオブジェクトfromのプロパティを読み取り、それらをオブジェクトontoにコピーする一般的なヘルパーです。同じオブジェクトontoを返しますが、両方のプロパティセットを含む新しい型を使用するため、実行時の動作を正しく記述します。

function merge<T1, T2>(onto: T1, from: T2): T1 & T2 {
    Object.keys(from).forEach(key => onto[key] = from[key]);
    return onto as T1 & T2;
}

この低レベルヘルパーはまだ型アサーションを実行しますが、設計上は型セーフです。このヘルパーを配置すると、OPの問題を完全な型安全性で解決するために使用できる演算子があります。

interface Foo {
    (message: string): void;
    bar(count: number): void;
}

const foo: Foo = merge(
    (message: string) => console.log(`message is ${message}`), {
        bar(count: number) {
            console.log(`bar was passed ${count}`)
        }
    }
);

TypeScriptプレイグラウンドで試してみるにはここをクリックfooFoo型に制約されているため、mergeの結果は完全なFooでなければなりません。したがって、barの名前をbadに変更すると、タイプエラーが発生します。

[〜#〜] nb [〜#〜]ただし、ここにはまだ1つのタイプの穴があります。 TypeScriptは、型パラメーターを「関数ではない」ように制約する方法を提供しません。そのため、混乱して関数をmergeの2番目の引数として渡すことができますが、それは機能しません。したがって、これが宣言されるまで、実行時にキャッチする必要があります。

function merge<T1, T2>(onto: T1, from: T2): T1 & T2 {
    if (typeof from !== "object" || from instanceof Array) {
        throw new Error("merge: 'from' must be an ordinary object");
    }
    Object.keys(from).forEach(key => onto[key] = from[key]);
    return onto as T1 & T2;
}
1

古い質問ですが、TypeScriptのバージョン3.1以降では、変数に関数宣言またはconstキーワードを使用する限り、単純なJSの場合と同じようにプロパティを割り当てることができます。

function f () {}
f.someValue = 3; // fine
const g = function () {};
g.someValue = 3; // also fine
var h = function () {};
h.someValue = 3; // Error: "Property 'someValue' does not exist on type '() => void'"

参照 および オンライン例

1
Geo1088

とても簡単だとは言えませんが、間違いなく可能です。

interface Optional {
  <T>(value?: T): OptionalMonad<T>;
  empty(): OptionalMonad<any>;
}

const Optional = (<T>(value?: T) => OptionalCreator(value)) as Optional;
Optional.empty = () => OptionalCreator();

興味があるなら これはOptionalのTypeScript/JavaScriptバージョンのGist of mineのものです

1
thiagoh

これは厳密な型指定とは異なりますが、次のことができます

var f: any = function() { }
f.someValue = 3;

私がこの質問を見つけたときのように、圧迫的な強い型付けを回避しようとしている場合。残念なことに、これはTypeScriptが完全に有効なJavaScriptで失敗するため、TypeScriptにバックオフするように指示する必要があります。

「あなたのJavaScriptは完全に有効なTypeScriptです」と評価されます。 (注:0.95を使用)

0
WillSeitz