2017-08-02 12 views
11

私はこのように定義されたActionタイプがあります。それは、各メンバーが有効なアクションであるユニオンタイプだです。TypeCheck createAction Reduxのヘルパー

type Action = 
    { type: 'DO_X' } 
    | 
    { type: 'DO_Y', payload: string } 
    | 
    { type: 'DO_Z', payload: number } 

を。

ここでtypeを受け入れ、​​を受け入れる新しい関数を返す関数createActionを作成したいと思います。私がしたいよう

const createAction = (type: Action['type']) => 
    (payload?: any) => 
    ({ type, payload }) 

それはtypeをtypechecks:

const doZ = createAction('DO_Z') 
console.log(doZ(42)) // { type: 'DO_Z', payload: 42 } 

は、ここに私の現在の実装です。​​の型チェックもどうすればできますか?私は​​がtypeに基づいて正しい行動の種類に一致するようにします。たとえば、doZは、stringで呼び出されたときには失敗します。その理由は、​​はnumberしか受け付けないと言うからです。

+1

'<タイプ、ペイロード>(タイプ:タイプ):(ペイロード:ペイロード)=> {タイプ:ペイロード:ペイロード}'のような定義を試したことがありますか? –

+0

@EmrysMyrooinそれは動作していないようです。タイプは値として使用されています。しかしジェネリックスは正しい方向への一歩と思われる。 TypeScriptは 'Type'と' Payload'が 'Action'から来ていることをどのように知っていますか? – daGrevis

+0

あなたはTypescriptを利用しています...私はタグをチェックしていません。私は実際に知っていない、私は入力のためのフローを使用している! –

答えて

4

この質問に対する標準的な答えは、使用の正確なケースによって異なります。あなたが書き込んだタイプに正確に評価するには、Actionが必要であると仮定します。つまり、type: "DO_X"のオブジェクトは、ではなく、は、いずれの種類のプロパティでもあります。​​です。これは、createAction("DO_X")が0引数の関数でなければならないことを意味し、createAction("DO_Y")は1つのstring引数の関数である必要があります。また、createAction()の型パラメータを自動的に推測して、たとえばBlahの値にはcreateAction<Blah>("DO_Z")を指定する必要はないと仮定します。これらの制限のいずれかが解消された場合、@Arnavionのようなものにソリューションを単純化することができます。


活字体は値プロパティからマッピングタイプが好きではありませんが、プロパティからそうキーを行うことが幸せです。ですから、Action型をコンパイラが私たちを助けるために使うことができるような型で構築しましょう。 (私はちょうどそれがどのように動作するかを示すために'DO_W'タイプを追加しました

type PayloadlessActionTypes = "DO_X" | "DO_W"; 

type ActionPayloads = { 
    DO_Y: string; 
    DO_Z: number; 
} 

のもペイロードのない任意のAction種類をご紹介しましょう:まず、私たちはこのようなそれぞれのアクションタイプのペイロードが説明します、削除することができます)。

今、私たちは最終的にActionを表現することができます:

type ActionMap = {[K in keyof ActionPayloads]: { type: K; payload: ActionPayloads[K] }} & {[K in PayloadlessActionTypes]: { type: K }}; 
type Action = ActionMap[keyof ActionMap]; 

ActionMapタイプは、キーが各Actiontypeであり、その値Action組合の対応する要素ですオブジェクトです。 Actionと​​との交差点、Actionとの交差点は​​ではありません。 Actionは、値のタイプがActionMapに過ぎません。 Actionが期待どおりであることを確認します。

を使用して、createAction()関数の入力に役立てることができます。ここでは、次のとおりです。

function createAction<T extends PayloadlessActionTypes>(type: T):() => ActionMap[T]; 
function createAction<T extends keyof ActionPayloads>(type: T): (payload: ActionPayloads[T]) => ActionMap[T]; 
function createAction(type: string) { 
    return (payload?: any) => (typeof payload === 'undefined' ? { type } : { type, payload }); 
} 

は、それはあなたが作成しているActiontypeに対応する型パラメータTとオーバーロードされた機能です。上の2つの宣言では、2つのケースが記述されています。がActiontypeで、​​を持たない場合、戻りタイプは、右タイプActionを返すゼロ引数の関数です。それ以外の場合は、正しい型の​​を受け取り、正しい型のActionを返す1つの引数の関数です。実装(第3署名と体が)に渡された無​​がない場合、それは結果に​​を追加しないことを除いて、あなたに似ている。


すべてやりました!私たちは、必要に応じて、それが動作することを確認できます。

var x = createAction("DO_X")(); // x: { type: "DO_X"; } 
var y = createAction("DO_Y")("foo"); // y: { type: "DO_Y"; payload: string; } 
var z = createAction("DO_Z")(5); // z: { type: "DO_Z"; payload: number; } 

createAction("DO_X")('foo'); // too many arguments 
createAction("DO_X")(undefined); // still too many arguments 
createAction("DO_Y")(5); // 5 is not a string 
createAction("DO_Z")(); // too few arguments 
createAction("DO_Z")(5, 5); // too many arguments 

あなたが行動on the TypeScript Playgroundでこのすべてを見ることができます。あなたのために働くことを願っています。がんばろう!

1

冗長が、これは動作します:

type XAction = { type: 'DO_X', payload: undefined }; 
type YAction = { type: 'DO_Y', payload: string }; 
type ZAction = { type: 'DO_Z', payload: number }; 

type Action = XAction | YAction | ZAction; 

const createAction = <T extends Action>(type: T['type']) => 
    (payload: T['payload']) => 
     ({ type, payload }); 

// Do compile: 

createAction<XAction>("DO_X")(undefined); 
createAction<YAction>("DO_Y")("foo"); 
createAction<ZAction>("DO_Z")(5); 

// Don't compile: 

createAction<XAction>("DO_X")(5); // Expected `undefined`, got number 
createAction<YAction>("DO_Y")(5); // Expected string, got number 
createAction<ZAction>("DO_X")(5); // Expected `"DO_Z"`, got `"DO_X"` 

簡単な方法(createActionの型パラメータを強制的にではない):Tが常にあるので、

type Action = { type: 'DO_X', payload: undefined } | { type: 'DO_Y', payload: string } | { type: 'DO_Z', payload: number }; 

createAction("DO_Y")("foo"); 

は、残念ながら、createAction<YAction>("DO_Y")(5)などをコンパイルすることができますActionと推定され、したがって​​パラメータはstring|number|undefinedです。

関連する問題