2017-04-11 96 views
3

我想定義一個類型,它是遞歸本身喜歡這個,主要有:打字稿遞歸類型與索引

interface CSSProperties { 
    marginLeft?: string | number 
    [key: string]?: CSSProperties 
} 

不幸的是,typescript docs說:

While string index signatures are a powerful way to describe the 「dictionary」 pattern, they also enforce that all properties match their return type. This is because a string index declares that obj.property is also available as obj[「property」]. In the following example, name’s type does not match the string index’s type, and the type-checker gives an error:

這似乎說這是不可能在打字稿中表達的,這似乎是一個嚴重的限制。 Flow在這裏做了我認爲正確的事情,並假定marginLeft不屬於索引規範。

這是可能的所有在TypeScript?另外,有沒有辦法指定一個字符串是任何字符串一組字符串?這樣,我可以做大致類似的事情:

interface NestedCSSProperties: CSSProperties { 
    [P not in keyof CSSProperties]?: CSSProperties 
} 
+0

感謝您的建議所有的往往過度看上去稍微更強大的替代品。不幸的是,我對建議的解決方法不感興趣,因爲它使得類型明顯不那麼健全。我打開了一個[問題](https://github.com/Microsoft/TypeScript/issues/15151),並建議以某種方式實現此目的。 –

回答

1

在這種情況下,您必須包含字符串和數字。然後你可以定義你的覆蓋。使用這個例子,如果你試圖給'marginLeft'分配一個地圖,TS會抱怨,但是會放任其他的東西。

任何更多的限制,你可能需要寫一個更深入的.d.ts文件,它可能使用映射類型。

您可以使用像Pick這樣的預設映射類型來簡化您的專屬屬性,但我承認,我有時候很難將我的大腦包裹在它們周圍。

interface IMap<T> { 
    [key: string]: T 
} 

type CSSPropertyValue = string | number | CSSPropertiesBase; 

interface CSSPropertiesBase extends IMap<CSSPropertyValue> 
{ 

} 

interface CSSProperties extends CSSPropertiesBase 
{ 
    marginLeft?: string|number; 
} 
+0

我敢肯定,你會遇到同樣的確切問題OP跑到與字符串|數字| undefined不能分配給CssPropertiesBase – dtabuenc

2

這裏的問題並不是真正用遞歸(這是允許的),而是與您指出的衝突簽名。

我不確定當前行爲的相反情況是否正確。這對我來說似乎是一個主觀的決定,可以通過兩種方式來實現。按原樣接受示例意味着您正在隱式合併定義;您可以查看其中一條線並假設界面的效果,而另一條線則更改結果。它確實感覺寫得更自然,但我不確定它是否安全,因爲你通常並不期望在類型定義上有所改進。

無論如何。 TypeScript確實允許類似於您期望的行爲,但您必須是明確的,因爲字符串鍵也可以是stringnumber類型。這將工作:

interface CSSProperties { 
    marginLeft?: string | number, 
    [key: string]: CSSProperties|string|number, 
} 

例如,與上面的接口,這是有效的:

let a: CSSProperties = { 
    marginLeft: 10, 
    name: { 
     marginLeft: 20, 
    } 
}; 

這不是:

let a: CSSProperties = { 
    marginLeft: 10, 
    something: false, // Type 'boolean' is not assignable to type 'string | number | CSSProperties'. 
    something: new RegExp(/a/g), // Type 'RegExp' is not assignable to type 'CSSProperties'. 
    name: { 
     marginLeft: 20, 
    }, 
    car: ["blue"], // Type 'string[]' is not assignable to type 'CSSProperties'. 
}; 

它會正確認識命名成員:

let name1: string | number = a.marginLeft; // OK, return type is string | number 
a.marginLeft = false; // Blocked, Type 'false' is not assignable to type 'string | number'. 
a["whatever"] = false; // Blocked, Type 'false' is not assignable to type 'string | number | CSSProperties'. 
a["marginLeft"] = false; // Blocked, Type 'false' is not assignable to type 'string | number'. 

這裏的問題,如何永遠是,你需要在閱讀時投出其他動態成員 - 它不會知道它是一個CSSProperties

這不會得到阻止:

a["whatever"] = 100; 

它會抱怨這樣的:

let name3: CSSProperties = a["name"]; // Type is CSSProperties | string | number 

但是,這將工作,如果你的類型轉換明確:

let name3: CSSProperties = a["name"] as CSSProperties; 
+0

我可以看到它是如何被認爲是主觀的,但它使這種類型無法表達的方式。你的建議是我現在用來解決問題的方法,但正如你所指出的那樣,它遠沒有聲音。事實上,我可以拼錯一個CSS屬性並且不會收到投訴。 –

0

有你嘗試使用交叉類型像這樣?

interface CssPropertyValues { 
    marginLeft? :string |number;  
} 

interface RecursiveCssProperties { 
    [key: string]: CssProperties; 
} 

type CssProperties = CssPropertyValues & RecursiveCssProperties; 

let foo: CssProperties = {}; 

let myMargin = foo.bar.really.foobar.marginLeft; //should work... myMargin is typed as string|number|undefined 

路口的類型是接口繼承

+0

heh,嘿丹。不幸的是,這不起作用,它具有與我的例子完全相同的效果,嘗試它w /:'let foo:CssProperties = {marginLeft:3}' –

+0

啊......奇怪的是,如果你訪問屬性, (例如'var foo:CssProperties = {}; foo.marginLeft = 3;') – dtabuenc