2017-08-02 73 views
11

我已經Action類型定義是這樣的:類型檢查createAction終極版幫手

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

這是一個聯合類型,其中每個成員都是有效的行動。

現在我想創建一個函數createAction,它接受type並返回一個接受​​的新函數。

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

這裏是我當前的實現:

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

它typechecks type像我想。我怎樣才能也typecheck​​?我想要​​根據type匹配正確操作的類型。例如,doZ在調用string時應該失敗,因爲它的​​表示它只接受number

+1

您是否嘗試過類似於(type:Type)的定義:(payload:Payload)=> {type:Type,payload:Payload}? –

+0

@EmrysMyrooin這似乎並不奏效。類型被用作值。但仿製藥似乎是朝着正確方向邁出的一步。 TypeScript如何知道'Type'和'Payload'來自'Action'? – daGrevis

+0

何你在Typescript ...我沒有檢查過標籤。我實際上不知道,我正在使用Flow進行打字! –

回答

4

這個問題的規範答案取決於你的確切用例。我假設你需要Action來準確評估你寫的類型;也就是type: "DO_X"的對象不是有​​任何形式的財產。這意味着createAction("DO_X")應該是零參數的函數,而createAction("DO_Y")應該是單個string參數的函數。我還將假設您想要自動推斷createAction()上的任何類型參數,因此您不需要爲Blah的任何值指定createAction<Blah>("DO_Z")。如果解除這些限制中的任何一個,則可以將解決方案簡化爲類似於@Arnavion提供的解決方案。


打字稿不喜歡物業映射類型,但它高興地物業這麼做。因此,讓我們以一種爲編譯器可用來幫助我們的類型提供類型的方式構建Action類型。首先,我們描述了有效載荷的每一個動作類型是這樣的:

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

我們還引入任何Action類型,而有效載荷:

type PayloadlessActionTypes = "DO_X" | "DO_W"; 

(我已經添加了'DO_W'型只是爲了顯示它是如何工作,但你可以刪除它)。

現在,我們終於能夠表達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 s與​​s的交點,以及Action而不是​​s的交點。而Action只是ActionMap的值類型。確認Action是您所期望的。

我們可以用ActionMap幫我們輸入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。前兩個聲明描述了兩種情況:如果TtypeAction而沒有​​,則返回類型是返回正確類型Action的零參數函數。否則,它是一個單參數函數,它採用正確類型的​​並返回正確類型Action。實施(第三簽名和身體)與你相似,不同之處在於,如果在沒有​​通過它不添加​​的結果。


全部完成!我們可以看到,它的工作原理是期望:

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類型參數):

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)等被編譯,因爲T總是推斷爲Action,因此​​參數是string|number|undefined