摘要:事件處理我們?cè)谶M(jìn)行事件注冊(cè)時(shí)經(jīng)常會(huì)在事件處理函數(shù)中使用事件對(duì)象,例如當(dāng)使用鼠標(biāo)事件時(shí)我們通過去獲取指針的坐標(biāo)。接收,其代表事件處理函數(shù)中對(duì)象的類型。小王沒有內(nèi)置從對(duì)象中排除是的屬性。
TypeScript 是 JS 類型的超集,并支持了泛型、類型、命名空間、枚舉等特性,彌補(bǔ)了 JS 在大型應(yīng)用開發(fā)中的不足,那么當(dāng) TypeScript 與 React 一起使用會(huì)碰撞出怎樣的火花呢?接下來讓我們一起探索在 TypeScript2.8+ 版本中編寫 React 組件的姿勢(shì)。前言
近幾年前端對(duì) TypeScript 的呼聲越來越高,Ryan Dahl 的新項(xiàng)目 Deno 中 TypeScript 也變成了一個(gè)必須要會(huì)的技能,知乎上經(jīng)常見到像『自從用了 TypeScript 之后,再也不想用 JavaScript 了』、『只要你用過 ES6,TypeScript 可以幾乎無門檻接入』、『TypeScript可以在任何場(chǎng)景代替 JS』這些類似的回答,抱著聽別人說不如自己用的心態(tài)逐漸嘗試在團(tuán)隊(duì)內(nèi)的一些底層支持的項(xiàng)目中使用 TypeScript。
使用 TypeScript 的編程體驗(yàn)真的是爽到爆,當(dāng)在鍵盤上敲下 . 時(shí),后面這一大串的提示真的是滿屏幕的幸福,代碼質(zhì)量和效率提升十分明顯,再也不想用 JavaScript 了。
在多帶帶使用 TypeScript 時(shí)沒有太大的坑,但是和一些框架結(jié)合使用的話坑還是比較多的,例如使用 React、Vue 這些框架的時(shí)候與 TypeScript 的結(jié)合會(huì)成為一大障礙,需要去查看框架提供的 .d.ts 的聲明文件中一些復(fù)雜類型的定義。本文主要聊一聊與 React 結(jié)合時(shí)經(jīng)常遇到的一些類型定義問題,閱讀本文建議對(duì) TypeScript 有一定了解,因?yàn)槲闹袑?duì)于一些 TypeScript 的基礎(chǔ)的知識(shí)不會(huì)有太過于詳細(xì)的講解。
編寫第一個(gè) TSX 組件import React from "react" import ReactDOM from "react-dom" const App = () => { return (Hello world) } ReactDOM.render(, document.getElementById("root")
上述代碼運(yùn)行時(shí)會(huì)出現(xiàn)以下錯(cuò)誤
Cannot find module "react"
Cannot find module "react-dom"
錯(cuò)誤原因是由于 React 和 React-dom 并不是使用 TS 進(jìn)行開發(fā)的,所以 TS 不知道 React、 React-dom 的類型,以及該模塊導(dǎo)出了什么,此時(shí)需要引入 .d.ts 的聲明文件,比較幸運(yùn)的是在社區(qū)中已經(jīng)發(fā)布了這些常用模塊的聲明文件 DefinitelyTyped 。
安裝 React、 React-dom 類型定義文件yarn add @types/react yarn add @types/react-dom
npm i @types/react -s npm i @types/react-dom -s有狀態(tài)組件開發(fā)
我們定義一個(gè) App 有狀態(tài)組件,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 可以對(duì) JSX 進(jìn)行解析,充分利用其本身的靜態(tài)檢查功能,使用泛型進(jìn)行 Props、 State 的類型定義。定義后在使用 this.state 和 this.props 時(shí)可以在編輯器中獲得更好的智能提示,并且會(huì)對(duì)類型進(jìn)行檢查。
那么 Component 的泛型是如何實(shí)現(xiàn)的呢,我們可以參考下 React 的類型定義文件 node_modules/@types/react/index.d.ts。
在這里可以看到 Component 這個(gè)泛型類, P 代表 Props 的類型, S 代表 State 的類型。
class Component{ readonly props: Readonly<{ children?: ReactNode }> & Readonly
; state: Readonly
; }
Component 泛型類在接收到 P , S 這兩個(gè)泛型變量后,將只讀屬性 props 的類型聲明為交叉類型 Readonly<{ children?: ReactNode }> & Readonly
; 使其支持 children 以及我們聲明的 color 、 size 。
通過泛型的類型別名 Readonly 將 props 的所有屬性都設(shè)置為只讀屬性。
Readonly 實(shí)現(xiàn)源碼 node_modules/typescript/lib/lib.es5.d.ts 。
由于 props 屬性被設(shè)置為只讀,所以通過 this.props.size = "sm" 進(jìn)行更新時(shí)候 TS 檢查器會(huì)進(jìn)行錯(cuò)誤提示,Error:(23, 16) TS2540: Cannot assign to "size" because it is a constant or a read-only property
React的 state 更新需要使用 setState 方法,但是我們經(jīng)常誤操作,直接對(duì) state 的屬性進(jìn)行更新。
this.state.count = 2
開發(fā)中有時(shí)候會(huì)不小心就會(huì)寫出上面這種代碼,執(zhí)行后 state 并沒有更新,我們此時(shí)會(huì)特別抓狂,心里想著我哪里又錯(cuò)了?
現(xiàn)在有了 TypeScript 我們可以通過將 state ,以及 state 下面的屬性都設(shè)置為只讀類型,從而防止直接更新 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
此時(shí)我們直接修改 state 值的時(shí)候 TypeScript 會(huì)立刻告訴我們錯(cuò)誤,Error:(23, 16) TS2540: Cannot assign to "count" because it is a constant or a read-only property. 。
無狀態(tài)組件開發(fā)props | 類型 | 是否必傳 |
---|---|---|
children | ReactNode | 否 |
onClick | function | 是 |
在 React 的聲明文件中 已經(jīng)定義了一個(gè) SFC 類型,使用這個(gè)類型可以避免我們重復(fù)定義 children、 propTypes、 contextTypes、 defaultProps、displayName 的類型。
實(shí)現(xiàn)源碼 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 進(jìn)行無狀態(tài)組件開發(fā)。
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
我們?cè)谶M(jìn)行事件注冊(cè)時(shí)經(jīng)常會(huì)在事件處理函數(shù)中使用 event 事件對(duì)象,例如當(dāng)使用鼠標(biāo)事件時(shí)我們通過 clientX、clientY 去獲取指針的坐標(biāo)。
大家可以想到直接把 event 設(shè)置為 any 類型,但是這樣就失去了我們對(duì)代碼進(jìn)行靜態(tài)檢查的意義。
function handleEvent (event: any) { console.log(event.clientY) }
試想下當(dāng)我們注冊(cè)一個(gè) Touch 事件,然后錯(cuò)誤的通過事件處理函數(shù)中的 event 對(duì)象去獲取其 clientY 屬性的值,在這里我們已經(jīng)將 event 設(shè)置為 any 類型,導(dǎo)致 TypeScript 在編譯時(shí)并不會(huì)提示我們錯(cuò)誤, 當(dāng)我們通過 event.clientY 訪問時(shí)就有問題了,因?yàn)? Touch 事件的 event 對(duì)象并沒有 clientY 這個(gè)屬性。
通過 interface 對(duì) event 對(duì)象進(jìn)行類型聲明編寫的話又十分浪費(fèi)時(shí)間,幸運(yùn)的是 React 的聲明文件提供了 Event 對(duì)象的類型聲明。
Event 事件對(duì)象類型常用 Event 事件對(duì)象類型:
ClipboardEvent
DragEvent
ChangeEvent
KeyboardEvent
MouseEvent
TouchEvent
WheelEvent
AnimationEvent
TransitionEvent
實(shí)例:
import { MouseEvent } from "react" interface IProps { onClick (event: MouseEvent): void, }
MouseEvent 類型實(shí)現(xiàn)源碼 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 類型實(shí)現(xiàn)源碼 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
當(dāng)我們定義事件處理函數(shù)時(shí)有沒有更方便定義其函數(shù)類型的方式呢?答案是使用 React 聲明文件所提供的 EventHandler 類型別名,通過不同事件的 EventHandler 的類型別名來定義事件處理函數(shù)的類型。
EventHandler 類型實(shí)現(xiàn)源碼 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 ,其代表事件處理函數(shù)中 event 對(duì)象的類型。
bivarianceHack 為事件處理函數(shù)的類型定義,函數(shù)接收一個(gè) event 對(duì)象,并且其類型為接收到的泛型變量 E 的類型, 返回值為 void。
實(shí)例:
interface IProps { onClick : MouseEventHandlerPromise 類型, }
在做異步操作時(shí)我們經(jīng)常使用 async 函數(shù),函數(shù)調(diào)用時(shí)會(huì) return 一個(gè) Promise 對(duì)象,可以使用 then 方法添加回調(diào)函數(shù)。
Promise
實(shí)例:
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 的類型。
然后聲明了一個(gè) 異步函數(shù) getResult 并且將函數(shù)返回值的類型定義為 Promise
最后調(diào)用 getResult 方法會(huì)返回一個(gè) promise 類型,通過 .then 調(diào)用,此時(shí) .then 方法接收的第一個(gè)回調(diào)函數(shù)的參數(shù) 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 的值只可以是數(shù)字 0、 1、 2 。
interface IProps { index: 0 | 1 | 2, }
Partial 實(shí)現(xiàn)源碼 node_modules/typescript/lib/lib.dom.d.ts
type Partial= { [P in keyof T]?: T[P] };
上面代碼的意思是 keyof T 拿到 T 所有屬性名, 然后 in 進(jìn)行遍歷, 將值賦給 P , 最后 T[P] 取得相應(yīng)屬性的值,中間的 ? 用來進(jìn)行設(shè)置為可選值。
如果 props 所有的屬性值都是可選的我們可以借助 Partial 這樣實(shí)現(xiàn)。
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 實(shí)現(xiàn)源碼 node_modules/typescript/lib/lib.dom.d.ts 。
type Required= { [P in keyof T]-?: T[P] };
看到這里,小伙伴們可能有些疑惑, -? 是做什么的,其實(shí) -? 的功能就是把 ? 去掉變成可選項(xiàng),對(duì)應(yīng)的還有 +? ,作用與 -? 相反,是把屬性變?yōu)榭蛇x項(xiàng)。
TypeScript2.8引入了條件類型,條件類型可以根據(jù)其他類型的特性做出類型的判斷。
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 實(shí)現(xiàn)源碼 node_modules/typescript/lib/lib.es5.d.ts 。
type Exclude= T extends U ? never : T;
實(shí)例:
type T = Exclude<1|2|3|4|5, 3|4> // T = 1|2|5
此時(shí) T 類型的值只可以為 1 、2 、 5 ,當(dāng)使用其他值是 TS 會(huì)進(jìn)行錯(cuò)誤提示。
Error:(8, 5) TS2322: Type "3" is not assignable to type "1 | 2 | 5".
從 T 中提取那些可以賦值給 U 的類型。
Extract實(shí)現(xiàn)源碼 node_modules/typescript/lib/lib.es5.d.ts。
type Extract= T extends U ? T : never;
實(shí)例:
type T = Exclude<1|2|3|4|5, 3|4> // T = 3|4
此時(shí)T類型的值只可以為 3 、4 ,當(dāng)使用其他值時(shí) TS 會(huì)進(jìn)行錯(cuò)誤提示:
Error:(8, 5) TS2322: Type "5" is not assignable to type "3 | 4".
從 T 中取出一系列 K 的屬性。
Pick 實(shí)現(xiàn)源碼 node_modules/typescript/lib/lib.es5.d.ts。
type Pick= { [P in K]: T[P]; };
實(shí)例:
假如我們現(xiàn)在有一個(gè)類型其擁有 name 、 age 、 sex 屬性,當(dāng)我們想生成一個(gè)新的類型只支持 name 、age 時(shí)可以像下面這樣:
interface Person { name: string, age: number, sex: string, } let person: Pick= { name: "小王", age: 21, }
將 K 中所有的屬性的值轉(zhuǎn)化為 T 類型。
Record 實(shí)現(xiàn)源碼 node_modules/typescript/lib/lib.es5.d.ts。
type Record= { [P in K]: T; };
實(shí)例:
將 name 、 age 屬性全部設(shè)為 string 類型。
let person: Record<"name" | "age", string> = { name: "小王", age: "12", }
從對(duì)象 T 中排除 key 是 K 的屬性。
由于 TS 中沒有內(nèi)置,所以需要我們使用 Pick 和 Exclude 進(jìn)行實(shí)現(xiàn)。
type Omit= Pick >
實(shí)例:
排除 name 屬性。
interface Person { name: string, age: number, sex: string, } let person: Omit= { age: 1, sex: "男" }
排除 T 為 null 、undefined。
NonNullable 實(shí)現(xiàn)源碼 node_modules/typescript/lib/lib.es5.d.ts。
type NonNullable= T extends null | undefined ? never : T;
實(shí)例:
type T = NonNullable; // string | string[]
獲取函數(shù) T 返回值的類型。。
ReturnType 實(shí)現(xiàn)源碼 node_modules/typescript/lib/lib.es5.d.ts。
type ReturnTypeany> = T extends (...args: any[]) => infer R ? R : any;
infer R 相當(dāng)于聲明一個(gè)變量,接收傳入函數(shù)的返回值類型。
實(shí)例:
type T1 = ReturnType<() => string>; // string type T2 = ReturnType<(s: string) => void>; // void
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/98008.html
摘要:面對(duì)越來越火的,我們公司今年也逐漸開始擁抱。綜上所述,我個(gè)人覺得是要?jiǎng)h除相關(guān)的東西,降低項(xiàng)目復(fù)雜度。但是有一個(gè)例外情況。這個(gè)配置項(xiàng)有三個(gè)值可選擇,分別是和。模式會(huì)生成,在使用前不需要再進(jìn)行轉(zhuǎn)換操作了,輸出文件的擴(kuò)展名為。 拋轉(zhuǎn)引用 現(xiàn)在越來越多的項(xiàng)目放棄了javascript,而選擇擁抱了typescript,就比如我們熟知的ant-design就是其中之一。面對(duì)越來越火的typesc...
摘要:系列引言最近準(zhǔn)備培訓(xùn)新人為了方便新人較快入手開發(fā)并編寫高質(zhì)量的組件代碼我根據(jù)自己的實(shí)踐經(jīng)驗(yàn)對(duì)組件設(shè)計(jì)的相關(guān)實(shí)踐和規(guī)范整理了一些文檔將部分章節(jié)分享了出來由于經(jīng)驗(yàn)有限文章可能會(huì)有某些錯(cuò)誤希望大家指出互相交流由于篇幅太長(zhǎng)所以拆分為幾篇文章主要有以 系列引言 最近準(zhǔn)備培訓(xùn)新人, 為了方便新人較快入手 React 開發(fā)并編寫高質(zhì)量的組件代碼, 我根據(jù)自己的實(shí)踐經(jīng)驗(yàn)對(duì)React 組件設(shè)計(jì)的相關(guān)實(shí)踐...
摘要:通過裝飾器或者利用時(shí)調(diào)用的函數(shù)來進(jìn)行使用下面代碼中當(dāng)或者發(fā)生變化時(shí),會(huì)監(jiān)聽數(shù)據(jù)變化確保通過觸發(fā)方法自動(dòng)更新。只能影響正在運(yùn)行的函數(shù),而無法影響當(dāng)前函數(shù)調(diào)用的異步操作參考官方文檔用法裝飾器函數(shù)遵循中標(biāo)準(zhǔn)的綁定規(guī)則。 前言: 本文基于React+TypeScript+Mobx+AntDesignMobile技術(shù)棧,使用Create-React-App腳手架進(jìn)行一個(gè)移動(dòng)端項(xiàng)目搭建,主要介紹項(xiàng)...
摘要:前面我們已經(jīng)說了大部分的核心內(nèi)容,接下來我們就說說如何用開發(fā)實(shí)際項(xiàng)目。因?yàn)楹徒Y(jié)合很緊密,資料也很多,而且我會(huì)找機(jī)會(huì)專門說下這方面的知識(shí),所以我們將重點(diǎn)放到如何用結(jié)合上來。所以前面打牢基礎(chǔ),現(xiàn)在我們開始實(shí)際組建工作流。 前面我們已經(jīng)說了大部分typescript的核心內(nèi)容,接下來我們就說說如何用typescript開發(fā)實(shí)際項(xiàng)目。 因?yàn)閍ngular和typescript結(jié)合很緊密,資料也...
閱讀 2243·2021-11-15 11:39
閱讀 1003·2021-09-26 09:55
閱讀 946·2021-09-04 16:48
閱讀 2858·2021-08-12 13:23
閱讀 931·2021-07-30 15:30
閱讀 2466·2019-08-29 14:16
閱讀 903·2019-08-26 10:15
閱讀 538·2019-08-23 18:40