原文連接: React, TypeScript and defaultProps dilemma
github 的地址 歡迎 star!css
如今大型的前端項目中是很須要注意可維護性,易讀性的,所以選擇使用「安全的」 JavaScript -- 即 Typescript 是很是有幫助的,你工做將會變得更簡單,避免一些潛在的錯誤問題。Typescript 搭配 React 能輕鬆的建立你的應用,配置好 tsconfig.json,那麼 Typescript 就會指導你寫出健壯的代碼。一切都是那麼順利,直到你遇到一個「大的」問題--處理組件中的 defaultProps
html
做者在 github 上發佈了 rex-tils代碼庫,其中包含了這篇文章討論的解決辦法前端
這篇文章是基於 TypeScript 2.9 版本以及開啓嚴格模式進行的。我將會演示這個問題以及組件中怎麼解決它react
咱們開始定義一個 Button 組件,遵循如下的 API 實現:git
Button API:github
import {MouseEvent, Component} from 'react';
import * as React from 'react';
type Props = {
onClick(e: MouseEvent<HTMLElement>): void;
color?: 'blue' | 'green' | 'red';
type?: 'button' | 'submit';
}
class Button extends Component<Props> {
static defaultProps = {
color: 'blue',
type: 'button'
};
render() {
const {onClick: handleClick, color, type, children} = this.props;
return (
<button
type={type}
style={{color}}
onClick={handleClick}
>
{children}
</button>
);
}
}
export default Button;
複製代碼
Button 組件實現typescript
如今,當咱們在其餘組件中使用 Button 時,編輯代碼就能獲取到相關的提示(可選項,必傳的 Props)json
可是,Button 組件的 defaultProps 屬性沒有被檢查,由於類型檢查器不能從泛型類擴展定義的靜態屬性中推斷它的類型。設計模式
具體的解釋:安全
static defaultProps
能夠經過分離 Props 提取出 color 和 type 的屬性,而後用類型交叉把默認值映射爲可選值。後面這步經過 TS 標準庫中 Partial 類型來快速實現。
而後顯示地指定 defaultProps 的類型是 DefaultProps
,這樣檢查器就能檢查 defaultProps。
我傾向於使用下面的方法,就是提取 defaultProps 和 initialState (若是組件有 state)來分離狀態類型,這樣作另外的好處——從實現裏面能夠獲取類型的明肯定義,減小了定義 props 的模板,只保留核心必選的功能。
接下來在組件裏面加入一點複雜的邏輯。 需求是,不但願只使用 css 內嵌樣式(這是反設計模式以及性能糟糕的),而是能基於 color 屬性,生成具備一些預約義的 css 類。
定義了 resolveColorTheme
函數,接受 color 的參數,返回自定義的 className。
TS Error:
Type 'undefined' is not assignable to type '"blue" | "green" | "red"'
複製代碼
可選的 props 致使的編譯錯誤
爲何?color 是可選的,編譯啓用的是嚴格模式,而聯合類型擴展存在 undefined/void 類型,可是函數不接受 undefined。
TypeScript 2.9 中提供了 4 中方法修復它:
這個方法是顯而易見的,就是明確告訴類型檢查器,這不會是 null 或者 undefined,經過!
操做符實現:
對於簡單的用例(props 屬性不多的,僅在 render 方法接受特定的 props 的用例)這樣作沒問題的,隨着業務的增加,這樣的方法會加重你組件的混亂,不可讀。你須要花費大量時間檢查哪一個 props 被定義爲 defaultProps,佔用了開發者大量時間,這樣也容易致使錯誤
那麼怎麼解決上一種問題的侷限性呢?咱們經過建立一個匿名類,斷言它的類型爲組合類型,設置 defaultProps 單個的類型(以及只讀類型限制),以下:
這能解決咱們當前的問題,但感受是怪異的。在 TypeScript 2.8 版本介紹了一種高級類型- 條件類型(conditional types)
T extends U ? X : Y
// 表示 T 若是繼承自 U,那麼它的類型就是 X,不然就是 Y
複製代碼
定義一個工廠函數/高階函數,用於 defaultProps 和 條件類型的 props 合併。
其中 withDefaultProps 就是一個高階函數,使用它,你不用明確地使用 React 的接口定義 defaultProps,另外若是不須要檢查 defaultProps,能夠刪除上面代碼中 type DefaultProps
。 可是要注意,它不能用於泛型組件,像這樣:
使用工廠/閉包模式來實現條件類型映射。
createPropsGetter 函數建立了一個閉包,經過泛型參數存儲/推斷出 defaultProps 類型。而後該函數返回了帶有 defaultProps 的 props,從 TS 運行時角度看,它返回的 props 和咱們傳遞的是相同的,所以 React 標準的 API就能獲取運行時 props 的獲取/解析。 以下實現: Button 組件的 defaultProps 類型檢查以及 props 實現像使用了
withDefaultProps
函數同樣,利用了類型映射結構,不過沒有將 defaultProps 映射爲可選的,由於在實現組件時它不是可選的。
更進一步的解釋:
createPropsGetter 實現組件類型的流程到此爲止,最終解決方案涵蓋了全部上面的問題:
若是有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝!