2017-11-09 15 views
1

私が得ようとしていることを説明するための例は次のとおりです。typescriptでユニオンタイプのフィルターを書く方法

const v1: { type: "S"; payload: string } = { type: "S", payload: "test" }; 
const v2: { type: "N"; payload: number } = { type: "N", payload: 123 }; 

type Actions = typeof v1 | typeof v2; 

const findByType = <A extends Actions>(type: A["type"]) => (
    action: Actions 
): action is A => action.type === type; 

const filterWithBothNameAndType = [v1, v2].filter(findByType<typeof v1>("S")); 
console.log(filterWithBothNameAndType[0].payload.trim()); 

const findByTypeDoesntWork = <A extends Actions, T extends A["type"]>(type: T) => (
    action: Actions 
): action is A => action.type === type; 

const filterWithJustType = [v1, v2].filter(findByTypeDoesntWork("S")); 
console.log(filterWithJustType[0].payload.trim()); 

typescript playground

私は右のタイプの情報を持っていると私は私が好きなAPIを持っていますが、それは情報を入力失い機能filterWithJustTypeを持っている機能findByTypeを持っています。私はAPIを汎用型を渡さずにただfilter("S")にします。これまでのところit looks likeはクラスとinstaceofでしか動作しませんでしたが、私はそれを普通のオブジェクトで動作させたいと思っていました。

答えて

0

それはActionsを拡張し、いくつかのタイプがリテラルタイプStypeメンバーを持っている場合、それはtypeof v1でなければならないという仮定を作るために、コンパイラを必要とするためには、動作しません。

extends Actionsはかなり弱い制約があり、これはコンパイルするために、一般的にこの仮定は、作るのは安全ではありません。余談として

const v3: { type: "S"; payload: boolean } = { type: "S", payload: false }; 

const filterWithJustType3 = [v1, v2, v3].filter(findByTypeDoesntWork("S")); 

、それはstrictFunctionTypesをオンにして2.6でコンパイルされませんが、型推論このオプションで導入された追加の健全性を利用していないため、それが必要であるかどうかは不明です。

しかし、コンパイラーに明示的に通知する方法は、ユニオンメンバーがそのタイプのメンバーから明示的に推論できることです(typeメンバー)。あなただけのタイプマッピングを自分で確立する必要があります:それはキーとペイロードタイプが同期していることを保証し、そして見よように、私のActionMapのアイデアを与えるため@artemする

const v1: { type: "S"; payload: string } = { type: "S", payload: "test" }; 
const v2: { type: "N"; payload: number } = { type: "N", payload: 123 }; 

interface ActionMap { 
    S: typeof v1; 
    N: typeof v2; 
} 

type Actions = ActionMap[keyof ActionMap]; 


const findByTypeWorks = <T extends keyof ActionMap>(type: T) => (
    action: Actions 
): action is ActionMap[T] => action.type === type; 

const filterWithJustType = [v1, v2].filter(findByTypeWorks("S")); 
console.log(filterWithJustType[0].payload.trim()); 
+0

が答えてくれてありがとう。このアプローチの多くの例で出版されたが、私がどのように正確なのActionMapチェックしませんので、コンパイラが好きではありませんI 'v1'の' type'を '' X''に変更することができます。これはコンパイルされますが、ランタイムエラーが返されます。また、ActionMapに ''と書くのは、アクションの数の代わりに 'アクションの数 '*'フィルタの数 'にスケーリングされるので、フィルタに書き込むよりもわずかに優れています。 'findByType'では、それは1つの場所にしかなりません:) bit.ly/runtime-error – JLarky

+0

はい、それは最適ではないことに同意します - 何らかの理由ですべての可能な' v1..N'型の型定義をActionMap変数を 'v1:ActionMap ['S'] = {...}'と 'v2:ActionMap ['N'] = {..}'として宣言します。意図したユースケースに合っているかどうかは不明です。 – artem

0

おかげで、私はactionCreatorを追加しました:

type ActionMap = { 
    S: string; 
    N: number; 
}; 

function actionCreatorFactory< 
    T extends keyof ActionMap, 
    P extends ActionMap[T] 
>(type: T) { 
    return function actionCreator(payload: P) { 
     return { type, payload }; 
    }; 
} 

const actionCreators = { 
    s: actionCreatorFactory("S"), 
    n: actionCreatorFactory("N"), 
}; 

const v1 = actionCreators.s("test"); 
const v2 = actionCreators.n(123); 

const findByType = < 
    T extends keyof ActionMap, 
    A extends { type: T; payload: ActionMap[T] } 
>(
    type: T 
) => (action: A): action is A => action.type === type; 

const filterWithJustType = [v1, v2].filter(findByType("S")); 
console.log(filterWithJustType[0].payload.trim()); 

この解決策では、型を宣言する必要がある場所はActionMapであることが保証されています。それ以外はすべてそれから派生します。

UPDATE: 条は、例えばので、ちょうどhttps://medium.com/@dhruvrajvanshi/some-tips-on-type-safety-with-redux-98588a85604c

関連する問題