摘要:事件處理我們在進行事件注冊時經常會在事件處理函數中使用事件對象,例如當使用鼠標事件時我們通過去獲取指針的坐標。接收,其代表事件處理函數中對象的類型。小王沒有內置從對象中排除是的屬性。
TypeScript 是 JS 類型的超集,并支持了泛型、類型、命名空間、枚舉等特性,彌補了 JS 在大型應用開發中的不足,那么當 TypeScript 與 React 一起使用會碰撞出怎樣的火花呢?接下來讓我們一起探索在 TypeScript2.8+ 版本中編寫 React 組件的姿勢。前言
近幾年前端對 TypeScript 的呼聲越來越高,Ryan Dahl 的新項目 Deno 中 TypeScript 也變成了一個必須要會的技能,知乎上經常見到像『自從用了 TypeScript 之后,再也不想用 JavaScript 了』、『只要你用過 ES6,TypeScript 可以幾乎無門檻接入』、『TypeScript可以在任何場景代替 JS』這些類似的回答,抱著聽別人說不如自己用的心態逐漸嘗試在團隊內的一些底層支持的項目中使用 TypeScript。
使用 TypeScript 的編程體驗真的是爽到爆,當在鍵盤上敲下 . 時,后面這一大串的提示真的是滿屏幕的幸福,代碼質量和效率提升十分明顯,再也不想用 JavaScript 了。
在多帶帶使用 TypeScript 時沒有太大的坑,但是和一些框架結合使用的話坑還是比較多的,例如使用 React、Vue 這些框架的時候與 TypeScript 的結合會成為一大障礙,需要去查看框架提供的 .d.ts 的聲明文件中一些復雜類型的定義。本文主要聊一聊與 React 結合時經常遇到的一些類型定義問題,閱讀本文建議對 TypeScript 有一定了解,因為文中對于一些 TypeScript 的基礎的知識不會有太過于詳細的講解。
編寫第一個 TSX 組件import React from "react" import ReactDOM from "react-dom" const App = () => { return (Hello world) } ReactDOM.render(, document.getElementById("root")
上述代碼運行時會出現以下錯誤
Cannot find module "react"
Cannot find module "react-dom"
錯誤原因是由于 React 和 React-dom 并不是使用 TS 進行開發的,所以 TS 不知道 React、 React-dom 的類型,以及該模塊導出了什么,此時需要引入 .d.ts 的聲明文件,比較幸運的是在社區中已經發布了這些常用模塊的聲明文件 DefinitelyTyped 。
安裝 React、 React-dom 類型定義文件yarn add @types/react yarn add @types/react-dom
npm i @types/react -s npm i @types/react-dom -s有狀態組件開發
我們定義一個 App 有狀態組件,props、 state 如下。
props | 類型 | 是否必傳 |
---|---|---|
color | string | 是 |
size | string | 否 |
props | 類型 |
---|---|
count | string |
使用 TSX 我們可以這樣寫
import * as React from "react"; interface IProps { color: string, size?: string, } interface IState { count: number, } class App extends React.Component{ public state = { count: 1, } public render () { return ( Hello world) } }
TypeScript 可以對 JSX 進行解析,充分利用其本身的靜態檢查功能,使用泛型進行 Props、 State 的類型定義。定義后在使用 this.state 和 this.props 時可以在編輯器中獲得更好的智能提示,并且會對類型進行檢查。
那么 Component 的泛型是如何實現的呢,我們可以參考下 React 的類型定義文件 node_modules/@types/react/index.d.ts。
在這里可以看到 Component 這個泛型類, P 代表 Props 的類型, S 代表 State 的類型。
class Component{ readonly props: Readonly<{ children?: ReactNode }> & Readonly
; state: Readonly
; }
Component 泛型類在接收到 P , S 這兩個泛型變量后,將只讀屬性 props 的類型聲明為交叉類型 Readonly<{ children?: ReactNode }> & Readonly
; 使其支持 children 以及我們聲明的 color 、 size 。
通過泛型的類型別名 Readonly 將 props 的所有屬性都設置為只讀屬性。
Readonly 實現源碼 node_modules/typescript/lib/lib.es5.d.ts 。
由于 props 屬性被設置為只讀,所以通過 this.props.size = "sm" 進行更新時候 TS 檢查器會進行錯誤提示,Error:(23, 16) TS2540: Cannot assign to "size" because it is a constant or a read-only property
React的 state 更新需要使用 setState 方法,但是我們經常誤操作,直接對 state 的屬性進行更新。
this.state.count = 2
開發中有時候會不小心就會寫出上面這種代碼,執行后 state 并沒有更新,我們此時會特別抓狂,心里想著我哪里又錯了?
現在有了 TypeScript 我們可以通過將 state ,以及 state 下面的屬性都設置為只讀類型,從而防止直接更新 state 。
import * as React from "react" interface IProps { color: string, size?: string, } interface IState { count: number, } class App extends React.PureComponent{ public readonly state: Readonly = { count: 1, } public render () { return ( Hello world) } public componentDidMount () { this.state.count = 2 } } export default App
此時我們直接修改 state 值的時候 TypeScript 會立刻告訴我們錯誤,Error:(23, 16) TS2540: Cannot assign to "count" because it is a constant or a read-only property. 。
無狀態組件開發props | 類型 | 是否必傳 |
---|---|---|
children | ReactNode | 否 |
onClick | function | 是 |
在 React 的聲明文件中 已經定義了一個 SFC 類型,使用這個類型可以避免我們重復定義 children、 propTypes、 contextTypes、 defaultProps、displayName 的類型。
實現源碼 node_modules/@types/react/index.d.ts 。
type SFC= StatelessComponent
; interface StatelessComponent
{ (props: P & { children?: ReactNode }, context?: any): ReactElement
| null; propTypes?: ValidationMap ; contextTypes?: ValidationMap
; defaultProps?: Partial ; displayName?: string; }
使用 SFC 進行無狀態組件開發。
import { SFC } from "react" import { MouseEvent } from "react" import * as React from "react" interface IProps { onClick (event: MouseEvent事件處理): void, } const Button: SFC = ({onClick, children}) => { return ( { children }) } export default Button
我們在進行事件注冊時經常會在事件處理函數中使用 event 事件對象,例如當使用鼠標事件時我們通過 clientX、clientY 去獲取指針的坐標。
大家可以想到直接把 event 設置為 any 類型,但是這樣就失去了我們對代碼進行靜態檢查的意義。
function handleEvent (event: any) { console.log(event.clientY) }
試想下當我們注冊一個 Touch 事件,然后錯誤的通過事件處理函數中的 event 對象去獲取其 clientY 屬性的值,在這里我們已經將 event 設置為 any 類型,導致 TypeScript 在編譯時并不會提示我們錯誤, 當我們通過 event.clientY 訪問時就有問題了,因為 Touch 事件的 event 對象并沒有 clientY 這個屬性。
通過 interface 對 event 對象進行類型聲明編寫的話又十分浪費時間,幸運的是 React 的聲明文件提供了 Event 對象的類型聲明。
Event 事件對象類型常用 Event 事件對象類型:
ClipboardEvent
DragEvent
ChangeEvent
KeyboardEvent
MouseEvent
TouchEvent
WheelEvent
AnimationEvent
TransitionEvent
實例:
import { MouseEvent } from "react" interface IProps { onClick (event: MouseEvent): void, }
MouseEvent 類型實現源碼 node_modules/@types/react/index.d.ts 。
interface SyntheticEvent{ bubbles: boolean; /** * A reference to the element on which the event listener is registered. */ currentTarget: EventTarget & T; cancelable: boolean; defaultPrevented: boolean; eventPhase: number; isTrusted: boolean; nativeEvent: Event; preventDefault(): void; isDefaultPrevented(): boolean; stopPropagation(): void; isPropagationStopped(): boolean; persist(): void; // If you thought this should be `EventTarget & T`, see https://github.com/DefinitelyTyped/DefinitelyTyped/pull/12239 /** * A reference to the element from which the event was originally dispatched. * This might be a child element to the element on which the event listener is registered. * * @see currentTarget */ target: EventTarget; timeStamp: number; type: string; } interface MouseEvent extends SyntheticEvent { altKey: boolean; button: number; buttons: number; clientX: number; clientY: number; ctrlKey: boolean; /** * See [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#keys-modifier). for a list of valid (case-sensitive) arguments to this method. */ getModifierState(key: string): boolean; metaKey: boolean; nativeEvent: NativeMouseEvent; pageX: number; pageY: number; relatedTarget: EventTarget; screenX: number; screenY: number; shiftKey: boolean; }
EventTarget 類型實現源碼 node_modules/typescript/lib/lib.dom.d.ts 。
interface EventTarget { addEventListener(type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void; dispatchEvent(evt: Event): boolean; removeEventListener(type: string, listener?: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean): void; }
通過源碼我們可以看到 MouseEvent
當我們定義事件處理函數時有沒有更方便定義其函數類型的方式呢?答案是使用 React 聲明文件所提供的 EventHandler 類型別名,通過不同事件的 EventHandler 的類型別名來定義事件處理函數的類型。
EventHandler 類型實現源碼 node_modules/@types/react/index.d.ts 。
type EventHandler> = { bivarianceHack(event: E): void }["bivarianceHack"]; type ReactEventHandler = EventHandler >; type ClipboardEventHandler = EventHandler >; type DragEventHandler = EventHandler >; type FocusEventHandler = EventHandler >; type FormEventHandler = EventHandler >; type ChangeEventHandler = EventHandler >; type KeyboardEventHandler = EventHandler >; type MouseEventHandler = EventHandler >; type TouchEventHandler = EventHandler >; type PointerEventHandler = EventHandler >; type UIEventHandler = EventHandler >; type WheelEventHandler = EventHandler >; type AnimationEventHandler = EventHandler >; type TransitionEventHandler = EventHandler >;
EventHandler 接收 E ,其代表事件處理函數中 event 對象的類型。
bivarianceHack 為事件處理函數的類型定義,函數接收一個 event 對象,并且其類型為接收到的泛型變量 E 的類型, 返回值為 void。
實例:
interface IProps { onClick : MouseEventHandlerPromise 類型, }
在做異步操作時我們經常使用 async 函數,函數調用時會 return 一個 Promise 對象,可以使用 then 方法添加回調函數。
Promise
實例:
interface IResponse{ message: string, result: T, success: boolean, } async function getResult (): Promise > { return { message: "獲取成功", result: [1, 2, 3], success: true, } } getResult() .then(result => { console.log(result.result) })
我們首先聲明 IResponse 的泛型接口用于定義 response 的類型,通過 T 泛型變量來確定 result 的類型。
然后聲明了一個 異步函數 getResult 并且將函數返回值的類型定義為 Promise
最后調用 getResult 方法會返回一個 promise 類型,通過 .then 調用,此時 .then 方法接收的第一個回調函數的參數 result 的類型為,{ message: string, result: number[], success: boolean} 。
Promise
interface Promise工具泛型使用技巧{ /** * Attaches callbacks for the resolution and/or rejection of the Promise. * @param onfulfilled The callback to execute when the Promise is resolved. * @param onrejected The callback to execute when the Promise is rejected. * @returns A Promise for the completion of which ever callback is executed. */ then (onfulfilled?: ((value: T) => TResult1 | PromiseLike ) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike ) | undefined | null): Promise ; /** * Attaches a callback for only the rejection of the Promise. * @param onrejected The callback to execute when the Promise is rejected. * @returns A Promise for the completion of the callback. */ catch (onrejected?: ((reason: any) => TResult | PromiseLike ) | undefined | null): Promise ; }
一般我們都是先定義類型,再去賦值使用,但是使用 typeof 我們可以把使用順序倒過來。
const options = { a: 1 } type Options = typeof options
限制 props.color 的值只可以是字符串 red、blue、yellow 。
interface IProps { color: "red" | "blue" | "yellow", }
限制 props.index 的值只可以是數字 0、 1、 2 。
interface IProps { index: 0 | 1 | 2, }
Partial 實現源碼 node_modules/typescript/lib/lib.dom.d.ts
type Partial= { [P in keyof T]?: T[P] };
上面代碼的意思是 keyof T 拿到 T 所有屬性名, 然后 in 進行遍歷, 將值賦給 P , 最后 T[P] 取得相應屬性的值,中間的 ? 用來進行設置為可選值。
如果 props 所有的屬性值都是可選的我們可以借助 Partial 這樣實現。
import { MouseEvent } from "react" import * as React from "react" interface IProps { color: "red" | "blue" | "yellow", onClick (event: MouseEvent): void, } const Button: SFC > = ({onClick, children, color}) => { return ( { children })
Required 實現源碼 node_modules/typescript/lib/lib.dom.d.ts 。
type Required= { [P in keyof T]-?: T[P] };
看到這里,小伙伴們可能有些疑惑, -? 是做什么的,其實 -? 的功能就是把 ? 去掉變成可選項,對應的還有 +? ,作用與 -? 相反,是把屬性變為可選項。
TypeScript2.8引入了條件類型,條件類型可以根據其他類型的特性做出類型的判斷。
T extends U ? X : Y
原先
interface Id { id: number, /* other fields */ } interface Name { name: string, /* other fields */ } declare function createLabel(id: number): Id; declare function createLabel(name: string): Name; declare function createLabel(name: string | number): Id | Name;
使用條件類型
type IdOrName= T extends number ? Id : Name; declare function createLabel (idOrName: T): T extends number ? Id : Name;
從 T 中排除那些可以賦值給 U 的類型。
Exclude 實現源碼 node_modules/typescript/lib/lib.es5.d.ts 。
type Exclude= T extends U ? never : T;
實例:
type T = Exclude<1|2|3|4|5, 3|4> // T = 1|2|5
此時 T 類型的值只可以為 1 、2 、 5 ,當使用其他值是 TS 會進行錯誤提示。
Error:(8, 5) TS2322: Type "3" is not assignable to type "1 | 2 | 5".
從 T 中提取那些可以賦值給 U 的類型。
Extract實現源碼 node_modules/typescript/lib/lib.es5.d.ts。
type Extract= T extends U ? T : never;
實例:
type T = Exclude<1|2|3|4|5, 3|4> // T = 3|4
此時T類型的值只可以為 3 、4 ,當使用其他值時 TS 會進行錯誤提示:
Error:(8, 5) TS2322: Type "5" is not assignable to type "3 | 4".
從 T 中取出一系列 K 的屬性。
Pick 實現源碼 node_modules/typescript/lib/lib.es5.d.ts。
type Pick= { [P in K]: T[P]; };
實例:
假如我們現在有一個類型其擁有 name 、 age 、 sex 屬性,當我們想生成一個新的類型只支持 name 、age 時可以像下面這樣:
interface Person { name: string, age: number, sex: string, } let person: Pick= { name: "小王", age: 21, }
將 K 中所有的屬性的值轉化為 T 類型。
Record 實現源碼 node_modules/typescript/lib/lib.es5.d.ts。
type Record= { [P in K]: T; };
實例:
將 name 、 age 屬性全部設為 string 類型。
let person: Record<"name" | "age", string> = { name: "小王", age: "12", }
從對象 T 中排除 key 是 K 的屬性。
由于 TS 中沒有內置,所以需要我們使用 Pick 和 Exclude 進行實現。
type Omit= Pick >
實例:
排除 name 屬性。
interface Person { name: string, age: number, sex: string, } let person: Omit= { age: 1, sex: "男" }
排除 T 為 null 、undefined。
NonNullable 實現源碼 node_modules/typescript/lib/lib.es5.d.ts。
type NonNullable= T extends null | undefined ? never : T;
實例:
type T = NonNullable; // string | string[]
獲取函數 T 返回值的類型。。
ReturnType 實現源碼 node_modules/typescript/lib/lib.es5.d.ts。
type ReturnTypeany> = T extends (...args: any[]) => infer R ? R : any;
infer R 相當于聲明一個變量,接收傳入函數的返回值類型。
實例:
type T1 = ReturnType<() => string>; // string type T2 = ReturnType<(s: string) => void>; // void
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/98008.html
摘要:面對越來越火的,我們公司今年也逐漸開始擁抱。綜上所述,我個人覺得是要刪除相關的東西,降低項目復雜度。但是有一個例外情況。這個配置項有三個值可選擇,分別是和。模式會生成,在使用前不需要再進行轉換操作了,輸出文件的擴展名為。 拋轉引用 現在越來越多的項目放棄了javascript,而選擇擁抱了typescript,就比如我們熟知的ant-design就是其中之一。面對越來越火的typesc...
摘要:系列引言最近準備培訓新人為了方便新人較快入手開發并編寫高質量的組件代碼我根據自己的實踐經驗對組件設計的相關實踐和規范整理了一些文檔將部分章節分享了出來由于經驗有限文章可能會有某些錯誤希望大家指出互相交流由于篇幅太長所以拆分為幾篇文章主要有以 系列引言 最近準備培訓新人, 為了方便新人較快入手 React 開發并編寫高質量的組件代碼, 我根據自己的實踐經驗對React 組件設計的相關實踐...
摘要:通過裝飾器或者利用時調用的函數來進行使用下面代碼中當或者發生變化時,會監聽數據變化確保通過觸發方法自動更新。只能影響正在運行的函數,而無法影響當前函數調用的異步操作參考官方文檔用法裝飾器函數遵循中標準的綁定規則。 前言: 本文基于React+TypeScript+Mobx+AntDesignMobile技術棧,使用Create-React-App腳手架進行一個移動端項目搭建,主要介紹項...
摘要:前面我們已經說了大部分的核心內容,接下來我們就說說如何用開發實際項目。因為和結合很緊密,資料也很多,而且我會找機會專門說下這方面的知識,所以我們將重點放到如何用結合上來。所以前面打牢基礎,現在我們開始實際組建工作流。 前面我們已經說了大部分typescript的核心內容,接下來我們就說說如何用typescript開發實際項目。 因為angular和typescript結合很緊密,資料也...
閱讀 2227·2021-11-15 11:39
閱讀 982·2021-09-26 09:55
閱讀 925·2021-09-04 16:48
閱讀 2831·2021-08-12 13:23
閱讀 919·2021-07-30 15:30
閱讀 2455·2019-08-29 14:16
閱讀 886·2019-08-26 10:15
閱讀 525·2019-08-23 18:40