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


83

いくつかのプロパティも保持されている関数オブジェクトを作成したいと思います。たとえば、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;

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


2
これはあなたの質問に対する直接の答えではありませんが、プロパティを使用して関数オブジェクトを簡潔に作成し、キャストに問題がない場合は、Object-Spread演算子がトリックを実行するようですvar f: { (): any; someValue: number; } = <{ (): any; someValue: number; }>{ ...(() => "Hello"), someValue: 3 };
ジョナサン

回答:


31

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

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

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

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

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


5
私が理解できる限り、これは実際には型をチェックしないので、.someValueは本質的に何でもかまいません。
shabunc 2015年

1
TypeScript 2.0の2017/2018の時点でのより良い/更新された回答は、Meirion Hughesによって以下に投稿されています:stackoverflow.com/questions/12766528/…
user3773048 2018年

107

更新:この回答は、TypeScriptの以前のバージョンでは最良の解決策でしたが、新しいバージョンではより良いオプションが利用可能です(他の回答を参照)。

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

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

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

// type error
f.notDeclard = 3

1
var f当時はsomeValueプロパティがないので、なぜ行がエラーにならないのかわかりません。

2
@torazaburoキャスト時にチェックしないので。チェックを入力するには、次のことを行う必要がありますvar f:F = function(){}。上記の例では失敗します。この答えは、割り当て段階で型チェックを失うため、より複雑な状況には適していません。
Meirion Hughes 2017

1
そうですが、関数式の代わりに関数宣言を使用してこれをどのように行うのですか?
アレクサンダーミルズ

1
これは、TSの最近のバージョンでは機能しなくなりました。これは、インターフェイスチェックがはるかに厳密になり、必要な 'someValue'プロパティがないために関数のキャストがコンパイルされなくなるためです。ただし、機能するのは<F><any>function() { ... }
galenus 2017

tslint:recommendedを使用すると、タイプキャストが機能せず、使用する必要があるのは次のとおりです。(関数を角かっこで囲んで式にします。その後、Fとして使用します):var f = (function () {}) as any as F;
NikhilWanpal 2017年

78

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

例:

ここに画像の説明を入力してください

ここでの魔法はObject.assign<T, U>(t: T, u: U)交差点 を返すために入力されることT & 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に
割り当てられませんエラー:numberはstring []に割り当てられません(戻り値の型)

警告:古いブラウザを対象とする場合は、Object.assignをポリフィルする必要がある場合があります。


1
素晴らしい、ありがとう。2017年2月の時点で、これがベストアンサーです(Typescript 2.xユーザーの場合)。
アンドリュー・フォークナー

今は2020年ですが、更新されたソリューションはありますか?
Archsx

46

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

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

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

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

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

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

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


回答の中でアンビエントモジュールについて言及することに賛成してください。これは、既存のJSモジュールを変換または注釈を付ける場合の非常に一般的なケースです。まさに私が探しているものです!
ニフェリス2016年

美しい。これをES6モジュールで使用したい場合は、function hello() { .. } namespace hello { export const value = 5; } export default hello;IMOを実行するだけで、Object.assignや同様のランタイムハックよりもはるかにクリーンです。ランタイムもタイプアサーションも何もありません。
skrebbel 2017

この答えは素晴らしいですが、Symbol.iteratorなどのSymbolを関数にどのようにアタッチしますか?
kzh 2017

上記のリンクは次のようになり、それ以上、正しく動作しませんtypescriptlang.org/docs/handbook/declaration-merging.html
iJungleBoy

宣言をマージすると、JavaScriptがかなり不安定になることを知っておく必要があります。これは、単純なプロパティの割り当てには過剰に思えるクロージャを追加します。これは、JavaScriptを使用しない方法です。また、あなたは関数がされた最初のものであることが確実である場合を除き、結果の値は必ずしも関数ではありません依存関係の問題の順序紹介する必要が
John Leidegren 2018

3

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

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

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

興味があれば、これはTypeScript / JavaScriptバージョンの私の要点からです。Optional


今は2020年ですが、更新されたソリューションはありますか?
Archsx

2

ショートカットとして、['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

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


1

更新された回答:を介して交差タイプが追加されたため、&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 Playgroundで試してみるには、ここをクリックしてくださいfoo型に制約されているFooため、の結果はmerge完全である必要があることに注意してくださいFoo。したがって、名前barをに変更するとbad、タイプエラーが発生します。

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

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

古い質問ですが、3.1以降のTypeScriptのバージョンで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'"

参照オンラインの例


0

これは強い型付けとは異なりますが、

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.