推斷函數返回值的類型
一個例子 ??
相信在 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,以此類推。
因為重複使用了"或",所以我們可以簡短的描述為,對於某些自然數n,n·n = 25。
如果我們再進一步專業的表述,那就可以寫作:
在這裡,我們可以很清楚的倒退 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&lt;F&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 |