推斷函數返回值的類型

一個例子 ??

相信在 TypeScript 和 React 的開發中,大家會經常遇到下面這樣一個場景

const mapToStateProps = (state: IStoreState) => ({ user: state.user})const mapToDispatchProps = { login,}interface IStateProps { user: { name: string }}type IDispatchProps = typeof IStatePropstype IProps = IStateProps & IDispatchPropsclass Login extends React.PureComponent<IProps> { render() { return null }}export default connect<IStateProps, IDispatchProps>( mapToStateProps, mapToDispatchProps,)(Login)

在這裡,由於 mapToStateProps 的返回值類型無法直接獲取,所以只有自己再手動重寫一遍 IStateProps 。

這樣的做法有點傻,畢竟我們還是希望它是可以自動推斷類型的。

那有沒有什麼辦法可以去做到呢?

容許我賣個小小的關子,如果直接說該怎麼做,這篇文章可太短了。(短不是好事)

所以我想先聊聊 Existential type。

Existential Type

Existential type,通常被翻譯作即存類型。

與 TypeScript 不同的是,Flow 支持了 existential type 的,所以我們可以看看在 Flow 里是如何運用 Existential Type 的。

function test(): Array<*> { return [1];}(test(): Array<number>);

Try Flow | test()

在 Flow 中,* 並不等同於 TypeScript 中的 any,而是表達了這裡需要一個佔位符,並儘可能通過上下文推斷佔位符的類型。

所以 Flow 通過對返回值的推理得出了這裡的 * 其實是 number。

所以即存類型更像是一個被動發動技能。

即存類型的原理來自於 Existential Quantification,符號為 ?。

Existential Quantification 的 wiki 頁面舉了這樣一個例子

假如你希望寫一個公式,它為真當且僅當某些公示自乘得25。

比如:

0·0 = 25, 或1·1 = 25, 或2·2 = 25, 或3·3 = 25,以此類推。

因為重複使用了"或",所以我們可以簡短的描述為,對於某些自然數nn·n = 25。

如果我們再進一步專業的表述,那就可以寫作:

詳見 https://en.wikipedia.org/wiki/Existential_quantification

在這裡,我們可以很清楚的倒退 n = 5。

同理在類型定義時,佔位符 * 代表了 n,再通過結果倒退 * 的類型,這也就是 Existential Type 的數學本質。

Flow

如果充分利用這個特性,就可以寫出如下的代碼來獲取函數返回值的類型。

type _ExtractReturn<B, F: (...args: any[]) => B> = B;type ExtractReturn<F> = _ExtractReturn<*, F>;type IStateProps = ExtractReturn<typeof mapToStateProps>

當然,Flow 有考慮到了我們有這樣的需求,所以增加了 $Call&amp;lt;F&amp;gt; 關鍵字。

TypeScript

可能你會好奇,同樣的方式寫的話,在 TypeScript 裡面會是什麼樣的呢?

如果把 * 替換成了 any,我們可以試一下。

type GenericConstraints<T> = (...args: any[]) => Ttype _ExtractReturn<B, F extends GenericConstraints<B>> = Btype ExtractReturn<F> = _ExtractReturn<any, F>// type IStateProps = anytype IStateProps = ExtractReturn<typeof mapToStateProps>

所以我們只能通過污染運行時的方式來解決這個問題了。

function $resultType<T>(expression: (...params: any[]) => T): T { return {} as T}const result = $resultType(mapToStateProps)// type IStateProps = {// user: { name: string }//}type IStateProps = typeof result

通過覆寫類型的方式來自動獲取函數返回值的類型。

由於污染了運行時,編譯到 JavaScript 後的代碼,相比 Flow,會多出一個 $resultType 函數和無用的 result 變數,

function $resultType(expression) { return {}}// result = {}const result = $resultType(mapToStateProps)

如果覺得噁心的話,可以在利用插件在生產環境打包時移除包含 $resultType 的代碼行。

總結

1. 為什麼要去推斷函數返回值的類型?

主要是因為我們需要重用已經定義好的類型。保持類型一次修改後,其他地方的代碼能夠正確的響應類型錯誤,而不只是僅僅做一個類型標記。

在上述的例子中,如果 IStoreState 變更了類型,手動寫的 IStateProps 無人去修改的話,組件中的類型定義就已經不準確了,很容易造成代碼隱患。

所以我們很有必要儘可能的去復用類型定義。

2. TypeScript 官方的進度

Proposal: Get the type of any expression with typeof #6606

Optional Generic Type Inference #14400

Granular inference for generic function type arguments #20122

官方的進度可以關注以上的幾個 issue,基本上還是討論的階段,沒有什麼實質性的進展。

所以以上的方案,我們可能還需要用一段時間。


推薦閱讀:

Hello RxJS
有哪些公司在使用或者準備使用Angular2?
如何進一步熟悉甚至掌握Angular?
angular 和 typescript 到底是否適合最佳實踐?
什麼時候選擇 Babel,什麼時候選擇 TypeScript?

TAG:TypeScript | React | ReactNative |