2012-10-07 69 views
39

我想創建一個函數對象,它也有一些屬性。例如在JavaScript中我會做:在TypeScript中使用屬性創建一個函數對象

var f = function() { } 
f.someValue = 3; 

現在打字稿我可以描述的此類型:

var f: {(): any; someValue: number; }; 

但是我不能實際建造它,而無需轉換。如:

var f: {(): any; someValue: number; } = 
    <{(): any; someValue: number; }>(
     function() { } 
    ); 
f.someValue = 3; 

如果沒有演員陣容,你會如何構建?

+1

這不是直接回答你的問題,但誰想要使用屬性簡潔地構建一個函數對象,並且可以通過強制轉換,[Object-Spread運算符](https://blog.mariusschulz.com/2016/12/23/typescript-2-1-object-rest-and -spread)似乎可以做到這一點:'var f:{():any; someValue:number; } = <{():any; someValue:number; }({} =>「你好」), someValue:3 };'。 – Jonathan

回答

14

所以,如果要求是簡單地構建和功能分配到「f」不進行強制轉換,這裏是一個可能的解決方案:

var f: {(): any; someValue: number; }; 

f = (() => { 
    var _f : any = function() { }; 
    _f.someValue = 3; 
    return _f; 
})(); 

本質上,它採用了一個自執行函數文本「構造」在分配完成之前將匹配該簽名的對象。唯一不可思議的是,函數的內部聲明需要是'any'類型的,否則編譯器會發出聲音,說你正在分配給對象中尚不存在的屬性。

編輯:簡化代碼。

+2

據我所知,這實際上不會檢查類型,所以.someValue本質上可以是任何東西。 – shabunc

1

作爲快捷方式,則可以動態地使用[「屬性」]訪問分配對象值:

var f = function() { } 
f['someValue'] = 3; 

這繞過類型檢查。

var val = f.someValue; // This won't work 
var val = f['someValue']; // Yeah, I meant to do that 

但是,如果你真的想要的類型檢查的屬性值,這是不行的:但是,因爲你要有意訪問屬性相同的方式,它是非常安全的。

68

更新:這個答案是早期版本的TypeScript中的最佳解決方案,但在更新的版本中有更好的選項可用(請參閱其他答案)。

接受的答案有效,在某些情況下可能需要,但缺點是沒有爲構建對象提供類型安全性。如果您嘗試添加未定義的屬性,此技術至少會引發類型錯誤。

interface F {(): any; someValue: number; } 

var f = <F>function() { } 
f.someValue = 3 

// type error 
f.notDeclard = 3 
+3

這是真棒,thx – peter

+1

也更容易閱讀 –

+1

我不明白爲什麼'var f'行不會導致錯誤,因爲那時沒有'someValue'屬性。 – 2016-07-14 16:39:51

0

這偏離了強類型,但你可以做

var f: any = function() { } 
f.someValue = 3; 

如果你想避開壓迫感強類型像我是當我發現這個問題。可悲的是,這是一種情況下TypeScript在完全有效的JavaScript上失敗,所以你不得不告訴TypeScript退後。

「您的JavaScript是完全有效的TypeScript」評估爲false。 (注意:使用0。95)

28

打字稿旨在通過declaration merging來處理這種情況:

you may also be familiar with JavaScript practice of creating a function and then extending the function further by adding properties onto the function. TypeScript uses declaration merging to build up definitions like this in a type-safe way.

宣言合併讓我們說什麼是兩種功能:和一個命名空間(內部模塊):

function f() { } 
namespace f { 
    export var someValue = 3; 
} 

這保留打字並讓我們寫出f()f.someValue。當編寫一個.d.ts文件現有的JavaScript代碼,使用declare

declare function f(): void; 
declare namespace f { 
    export var someValue: number; 
} 

添加屬性的功能往往是打字稿混亂或意外的模式,所以要儘量避免它,但使用或將舊的時候,可能有必要JS代碼。這是將內部模塊(名稱空間)與外部模塊混合的唯一時機之一。

+0

Upvote在答案中提到環境模塊。轉換或註釋現有JS模塊時,這是非常典型的情況。正是我在找什麼! – Nipheris

+0

可愛。如果你想使用ES6模塊,你可以使用'''function hello(){..}''' '''命名空間hello { export const value = 5; }''' '''export default hello;''' IMO比Object.assign或類似的運行時黑客清潔得多。沒有運行時,沒有類型斷言,沒有什麼。 – skrebbel

+0

這個答案很好,但是如何將Symbol.iterator等Symbol添加到函數中呢? – kzh

1

更新的答案:因爲通過&的加分交叉點的類型,就可以「合併」兩種推斷類型的飛行。

這裏有一個一般的幫手了一個對象onto讀一些對象from,並將它們複製的特性。它返回相同的對象onto但一個新類型,包括套房產,所以正確描述運行時行爲:

function merge<T1, T2>(onto: T1, from: T2): T1 & T2 { 
    Object.keys(from).forEach(key => onto[key] = from[key]); 
    return onto as T1 & T2; 
} 

這種低級別的幫手也仍然執行一個類型的斷言,但它是類型 - 設計安全。有了這個幫手的地方,我們有一個運營商,我們可以使用全型安全解決OP的問題:

interface Foo { 
    (message: string): void; 
    bar(count: number): void; 
} 

const foo: Foo = merge(
    (message: string) => console.log(`message is ${message}`), { 
     bar(count: number) { 
      console.log(`bar was passed ${count}`) 
     } 
    } 
); 

Click here to try it out in the TypeScript Playground。請注意,我們將foo限制爲Foo類型,因此merge的結果必須是完整的Foo。因此,如果您將bar重命名爲bad,那麼您會收到類型錯誤。

NB還有這裏一個類型孔,但是。 TypeScript沒有提供一種將類型參數限制爲「不是函數」的方法。所以你可能會感到困惑,並將你的函數作爲merge的第二個參數傳遞,那就行不通了。所以,直到可以聲明,我們必須抓住它在運行時:

function merge<T1, T2>(onto: T1, from: T2): T1 & T2 { 
    if (typeof from !== "object" || from instanceof Array) { 
     throw new Error("merge: 'from' must be an ordinary object"); 
    } 
    Object.keys(from).forEach(key => onto[key] = from[key]); 
    return onto as T1 & T2; 
} 
21

這是很容易實現,現在(打字稿2.X)與Object.assign(target, source)

例如:

enter image description here

這裏的魔術是Object.assign<T, U>(...)鍵入返回工會類型的T & U

強制執行,這解析爲一個已知的接口也直接:

interface Foo { 
    (a: number, b: string): string[]; 
    foo: string; 
} 

let method: Foo = Object.assign(
    (a: number, b: string) => { return a * a; }, 
    { foo: 10 } 
); 

Error: foo:number not assignable to foo:string
Error: number not assignable to string[] (return type)

警告:你可以,如果靶向舊的瀏覽器需要polyfill Object.assign。

+0

非常棒,謝謝。截至2017年2月,這是最好的答案(對於Typescript 2.x用戶)。 –

相關問題