2017-08-03 48 views
2

我有一個TypedMapper實用類,它可以:接受T或陣列<T>甚至不與聯盟類型工作

  • 類型T
  • 散列陣列的散列/字典輸入/字典鍵入T[]

從流量的角度來看,它看起來是這樣的:

flow diagram

,並使這些二元結構作爲輸入/輸出,我有以下的地圖()函數:

public map() { 

    return Array.isArray(this._inputData) 
    ? this._inputData.map((item: T) => this.convert(item)) as T[] 
    : this.convert(this._inputData) as T; 
} 

的TypedMapper級工作正常,但是當我使用它,我想它的地圖( )函數返回離散類型TT[]不是這兩種類型的聯合。例如,在下面的單元測試,我們得到成功JS導致的類型不知道是TT[]

const config = { 
    passThroughs: ['foo', 'bar', 'baz'], 
    defaults: { 
    foo: 12345 
    } 
}; 
const data = { 
    bar: 'hello world' 
}; 
interface IFooBarBaz { 
    foo: number; 
    bar: string; 
    baz: string; 
} 
const mapped = new TypedMapper<IFooBarBaz>(data, config).map(); 
expect(mapped.foo).to.equal(12345); 
expect(mapped.bar).to.equal('hello world'); 
expect(Object.keys(mapped)).to.include('baz'); 
expect(mapped.baz).to.equal(undefined); 

如下面的截圖所示:

ts error

燦任何人都可以幫助我理解如何確保 - 基於輸入結構是否是數組 - 輸出數據結構是已知的離散數據結構?

回答

3

這是一個非常有趣的問題。如果TypeScript執行type inference on optional generic types;那麼我認爲我們可以將TypedMapper<T>擴展爲TypedMapper<T,D extends T | T[]>,並允許從構造函數推斷D。那麼map()的輸出將是D,你就完成了。

但它沒有。

什麼你可以做的就是這樣的事情:創建TypedMapper<T>兩個子類(或子接口),像這樣:

interface ArrayTypedMapper<T> extends TypedMapper<T> { 
    map(): T[] 
} 
interface NonArrayTypedMapper<T> extends TypedMapper<T> { 
    map(): T 
} 

,並創建您使用的構造方法,而不是一個靜態方法:

class TypedMapper<T> { 
... 
    static make<T>(inputData: Partial<T>[], config: any) : ArrayTypedMapper<T> 
    static make<T>(inputData: Partial<T>, config: any): NonArrayTypedMapper<T> 
    static make<T>(inputData: Partial<T>[] | Partial<T>, config: any): TypedMapper<T> { 
    return new TypedMapper<T>(inputData, config); 
    } 

現在重載make功能將認識如果inputData爲數組或不併返回一個變窄TypeMapper<T>類型:

const mapped = TypedMapper.make<IFooBarBaz>(data, config).map(); 
// mapped: IFooBarBaz 
const mappedArr = TypedMapper.make<IFooBarBaz>([data], config).map(); 
// mappedArr: IFooBarBaz[] 

這與我所能得到的差不多。可能還有其他方法可以做到,但我實際上可以開展工作的所有方法都涉及TypedMapper<T>的特殊子類。

希望有所幫助。祝你好運!

0

我給你一些(可能不理想)的選擇:

第一個選擇是明確施放該映射值:

expect((<IFooBarBaz>mapped).bar).to.equal('hello world'); 

一個更有趣的選擇是把它包在一個類型的後衛。

if(!Array.isArray(mapped)) { 
     expect(mapped.bar).to.equal('hello world'); // No explicit casting! 
     expect(mapped.baz).to.equal(undefined); 
    } else { 
     // Fail the test here! 
    } 

在這種情況下,打字稿編譯器足夠聰明應用邏輯:由於if塊內的代碼,如果mapped不是數組僅運行,mapped必須類型的IFooBarBaz


我不知道這是否被視爲'輸出結構是已知的離散'。

+0

顯式類型轉換那種違背了泛型類型,我的目的看看你建議的外部類型守護進程是如何工作的,但是它出現在我不希望避免的不必要的運行時條件下。隱含地理解現有的_內部類型守衛沒有辦法嗎? – ken

1

據我所知,類型推斷不會跨越函數邊界。

你可以表達你想要什麼overloads,但是對於map()必須data作爲參數,然後就可以申報兩個重載map,一個拍攝目標並返回對象,另一個以數組和返回數組。

很難給出確切的答案,因爲你不給定義你的問題TypedMapper,但這樣的事情可以工作:

class TypedMapper<T, D> { 
    constructor(public config: { passThroughs: string[], defaults: Partial<T> }) { 
    } 
    public map(d: D): T; 
    public map(d: D[]): T[]; 
    public map(d: D | D[]): T| T[] { 
     return Array.isArray(d) 
       ? d.map((item: D) => this.convert(item)) as T[] 
       : this.convert(d); 
    } 
    public convert(data: D): T { 
     return undefined; 
    } 
} 

const config = { 
    passThroughs: ['foo', 'bar', 'baz'], 
    defaults: { foo: 12345 } 
}; 
const data = { 
    bar: 'hello world' 
}; 

interface IFooBarBaz { 
    foo: number; 
    bar: string; 
    baz: string; 
} 

const mapped = new TypedMapper<IFooBarBaz, typeof data>(config).map(data); 
mapped.foo === 12345; 
mapped.bar === 'hello world'; 

const a = [data, data]; 

const mappedArray = new TypedMapper<IFooBarBaz, typeof data>(config).map(a); 
mappedArray[0].foo === 12345;