【譯】React, TypeScript 中 defaultProps 的類型解決

原文連接: React, TypeScript and defaultProps dilemma
github 的地址 歡迎 star!css

前言

如今大型的前端項目中是很須要注意可維護性,易讀性的,所以選擇使用「安全的」 JavaScript -- 即 Typescript 是很是有幫助的,你工做將會變得更簡單,避免一些潛在的錯誤問題。Typescript 搭配 React 能輕鬆的建立你的應用,配置好 tsconfig.json,那麼 Typescript 就會指導你寫出健壯的代碼。一切都是那麼順利,直到你遇到一個「大的」問題--處理組件中的 defaultPropshtml

做者在 github 上發佈了 rex-tils代碼庫,其中包含了這篇文章討論的解決辦法前端

這篇文章是基於 TypeScript 2.9 版本以及開啓嚴格模式進行的。我將會演示這個問題以及組件中怎麼解決它react

一個 Button 組件

咱們開始定義一個 Button 組件,遵循如下的 API 實現:git

Button API:github

  • onClick (點擊事件)
  • color (定義按鈕顏色)
  • type (按鈕的類型有 ‘button’ 或者 ‘submit’) 咱們將 color 和 type 定義爲可選項,用 defaultProps 定義他們的默認值(組件的使用者可能不會添加這些參數)
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 沒有類型檢查

能夠經過分離 Props 提取出 color 和 type 的屬性,而後用類型交叉把默認值映射爲可選值。後面這步經過 TS 標準庫中 Partial 類型來快速實現。

而後顯示地指定 defaultProps 的類型是 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 中方法修復它:

  • 非空斷言語句(Non-null assertion operator)
  • 組件類型重置(Component type casting)
  • 高階組件定義 defaultProps
  • Props getter 函數

1. 非空斷言語句

這個方法是顯而易見的,就是明確告訴類型檢查器,這不會是 null 或者 undefined,經過!操做符實現:

在 render 方法中使用非空斷言語句

對於簡單的用例(props 屬性不多的,僅在 render 方法接受特定的 props 的用例)這樣作沒問題的,隨着業務的增加,這樣的方法會加重你組件的混亂,不可讀。你須要花費大量時間檢查哪一個 props 被定義爲 defaultProps,佔用了開發者大量時間,這樣也容易致使錯誤

2. 組件類型重置

那麼怎麼解決上一種問題的侷限性呢?咱們經過建立一個匿名類,斷言它的類型爲組合類型,設置 defaultProps 單個的類型(以及只讀類型限制),以下:

這能解決咱們當前的問題,但感受是怪異的。

在 TypeScript 2.8 版本介紹了一種高級類型- 條件類型(conditional types)

T extends U ? X : Y
// 表示 T 若是繼承自 U,那麼它的類型就是 X,不然就是 Y
複製代碼

3. 高階函數定義 defaultProps

定義一個工廠函數/高階函數,用於 defaultProps 和 條件類型的 props 合併。

其中 withDefaultProps 就是一個高階函數,使用它,你不用明確地使用 React 的接口定義 defaultProps,另外若是不須要檢查 defaultProps,能夠刪除上面代碼中 type DefaultProps 。 可是要注意,它不能用於泛型組件,像這樣:

你在泛型組件上使用了高階函數(withDefaultProps 函數),會致使它的泛型丟失,

4. props getter 函數

使用工廠/閉包模式來實現條件類型映射。

像使用了 withDefaultProps 函數同樣,利用了類型映射結構,不過沒有將 defaultProps 映射爲可選的,由於在實現組件時它不是可選的。

createPropsGetter 函數建立了一個閉包,經過泛型參數存儲/推斷出 defaultProps 類型。而後該函數返回了帶有 defaultProps 的 props,從 TS 運行時角度看,它返回的 props 和咱們傳遞的是相同的,所以 React 標準的 API就能獲取運行時 props 的獲取/解析。 以下實現:

Button component with properly typed defaultProps and implementation props
Button 組件的 defaultProps 類型檢查以及 props 實現

更進一步的解釋:

createPropsGetter 實現組件類型的流程

到此爲止,最終解決方案涵蓋了全部上面的問題:

  • 不須要使用非空斷言語句來避開類型檢查
  • 不須要將組件強制間接地轉換爲其餘類型
  • 不須要再次建立組件,從而不會再進程中丟失類型
  • 泛型組件也能使用
  • 易於推理,TypeScript 3.0 版本支持

若是有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝!

參考

  1. TS 官網高級類型
  2. TS 一些工具泛型的使用及其實現
相關文章
相關標籤/搜索