import React from 'react'
import ReactDOM from 'react-dom'
const App = () => {
return (
<div>Hello World</div>
)
}
ReactDOM.render(<App/>, document.getElementById('root'))
複製代碼
上述代碼運行時會出現如下錯誤node
Cannot find module 'react'
Cannot find module 'react-dom'
錯誤緣由是因爲React
和React-dom
並非使用TS 進行開發的,因此 TS 不知道 React、 React-dom 的類型,以及該模塊導出了什麼,此時須要引入 .d.ts 的聲明文件,比較幸運的是在社區中已經發布了這些經常使用模塊的聲明文件 DefinitelyTypedreact
React
、 React-dom
類型定義文件使用yarn安裝git
yarn add @types/react
yarn add @types/react-dom
複製代碼
使用npm安裝github
npm i @types/react -s
npm i @types/react-dom -s
複製代碼
咱們定義一個 App 有狀態組件,props
、 state
以下typescript
props | 類型 | 是否必傳 |
---|---|---|
color |
string | 是 |
size |
string | 否 |
state | 類型 |
---|---|
count |
string |
使用TSX咱們能夠這樣寫shell
import * as React from 'react'
interface IProps {
color: string,
size?: string,
}
interface IState {
count: number,
}
class App extends React.Component<IProps, IState> {
public state = {
count: 1,
}
public render () {
return (
<div>Hello world</div>
)
}
}
複製代碼
TypeScript 能夠對 JSX 進行解析,充分利用其自己的靜態檢查功能,使用泛型進行 Props、 State 的類型定義。npm
那麼 Component 的泛型是如何實現的呢,咱們能夠參考下 React 的類型定義文件 node_modules/@types/react/index.d.ts
promise
P
表明 Props
的類型,S
表明 State
的類型dom
class Component<P, S> {
readonly props: Readonly<{ children?: ReactNode }> & Reactonly<P>
state: Reactonly<S>
}
複製代碼
Component泛型類在接收到 P
、S
這兩個範型變量後,將只讀屬性 props
的類型聲明爲交叉類型 readonly props: Readonly<{ children?: ReactNode }> & Reactonly<P>
使其支持 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
state
React的 state
更新須要使用 setState
方法,可是咱們常常誤操做,直接對 state
的屬性進行更新。
this.state.count = 2
複製代碼
咱們能夠經過將 state
,以及 state
下面的屬性都設置爲只讀屬性,從而防止直接更新 state
import * as React from 'react'
interface IProps {
color: string,
size?: string,
}
interface IState {
count: number,
}
class App extends React.PureComponent<IProps, IState> {
public readonly state: Readonly<IState> = {
count: 1,
}
public render () {
return (
<div>Hello world</div>
)
}
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聲明文件中,已經定義了一個FC類型,使用這個類型能夠避免咱們重複定義 propTypes
、contextTypes
、defaultProps
、displayName
的類型。
實現源碼 node_modules/@types/react/index.d.ts
type FC<P = {}> = FunctionComponent<P>;
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): ReactElement | null;
propTypes?: WeakValidationMap<P>;
contextTypes?: ValidationMap<any>;
defaultProps?: Partial<P>;
displayName?: string;
}
複製代碼
使用 FC
進行無狀態組件開發
import * as React from 'react'
import { MouseEvent } from 'react'
interface IProps {
children?: React.ReactNode
onClick (event: MouseEvent<HTMLDivElement>): void
}
const Button: React.FC<Iprops> = ({onClick, children}) => {
return (
<div onClick={onClick}>
{ children }
</div>
)
}
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
對象的類型聲明。
ClipboardEvent<T = Element>
剪切板事件對象
DragEvent<T =Element>
拖拽事件對象
ChangeEvent<T = Element>
Change事件對象
KeyboardEvent<T = Element>
鍵盤事件對象
MouseEvent<T = Element>
鼠標事件對象
TouchEvent<T = Element>
觸摸事件對象
WheelEvent<T = Element>
滾輪時間對象
AnimationEvent<T = Element>
動畫事件對象
TransitionEvent<T = Element>
過渡事件對象
實例:
import { MouseEvent } from 'react'
interface IProps {
onClick (event: MouseEvent<HTMLDivElement>): void,
}
複製代碼
MouseEvent
類型實現源碼 node_modules/@types/react/index.d.ts
interface SyntheticEvent<T = Element> {
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<T = Element> extends SyntheticEvent<T> {
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<T = Element>
繼承 SyntheticEvent<T>
,而且經過 T
接收一個 DOM
元素的類型, currentTarget
的類型由 EventTarget & T
組成交叉類型。
當咱們定義事件處理函數時有沒有更方便定義其函數類型的方式呢?答案是使用 React
聲明文件所提供的 EventHandler
類型別名,經過不一樣事件的 EventHandler
的類型別名來定義事件處理函數的類型。
EventHandler
類型實現源碼 node_modules/@types/react/index.d.ts
type EventHandler<E extends SyntheticEvent<any>> = { bivarianceHack(event: E): void }["bivarianceHack"];
type ReactEventHandler<T = Element> = EventHandler<SyntheticEvent<T>>;
type ClipboardEventHandler<T = Element> = EventHandler<ClipboardEvent<T>>;
type DragEventHandler<T = Element> = EventHandler<DragEvent<T>>;
type FocusEventHandler<T = Element> = EventHandler<FocusEvent<T>>;
type FormEventHandler<T = Element> = EventHandler<FormEvent<T>>;
type ChangeEventHandler<T = Element> = EventHandler<ChangeEvent<T>>;
type KeyboardEventHandler<T = Element> = EventHandler<KeyboardEvent<T>>;
type MouseEventHandler<T = Element> = EventHandler<MouseEvent<T>>;
type TouchEventHandler<T = Element> = EventHandler<TouchEvent<T>>;
type PointerEventHandler<T = Element> = EventHandler<PointerEvent<T>>;
type UIEventHandler<T = Element> = EventHandler<UIEvent<T>>;
type WheelEventHandler<T = Element> = EventHandler<WheelEvent<T>>;
type AnimationEventHandler<T = Element> = EventHandler<AnimationEvent<T>>;
type TransitionEventHandler<T = Element> = EventHandler<TransitionEvent<T>>;
複製代碼
EventHandler
接收 E
,其表明事件處理函數中 event
對象的類型。
bivarianceHack
爲事件處理函數的類型定義,函數接收一個 event
對象,而且其類型爲接收到的泛型變量 E
的類型, 返回值爲 void
。
實例:
interface IProps {
onClick: MouseEventHandler<HTMLDivElement>
}
複製代碼
在作異步操做時咱們常用 async
函數,函數調用時會 return
一個 Promise
對象,可使用 then
方法添加回調函數。
Promise<T>
是一個泛型類型,T
泛型變量用於肯定 then
方法時接收的第一個回調函數(onfulfilled)的參數類型。
實例:
interface IResponse<T> {
message: string
result: T,
success: boolean
}
async function getResponse(): Promise<IResponse<number[]>> {
return {
message: '獲取成功',
result: [1, 2, 3],
success: true,
}
}
getResponse()
.then(response => {
console.log(response.result)
})
複製代碼
咱們首先聲明 IResponse
的泛型接口用於定義 response
的類型,經過 T
泛型變量來肯定 result
的類型。
而後聲明瞭一個 異步函數 getResponse
而且將函數返回值的類型定義爲 Promise<IResponse<number[]>>
。
最後調用 getResponse
方法會返回一個 promise
類型,經過 then
調用,此時 then
方法接收的第一個回調函數的參數 response
的類型爲,{ message: string, result: number[], success: boolean}
。
Promise<T>
實現源碼 node_modules/typescript/lib/lib.es5.d.ts
。
interface Promise<T> {
/** * 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<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>; /** * 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<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>; } 複製代碼
實例:
interface StringArray {
[index: number]: string
}
let myArray: StringArray
myArray = ["Bob", "Fred"]
let myStr: string = myArray[0]
複製代碼
上面例子裏,咱們定義了 StringArray
接口,它具備索引簽名。 這個索引簽名表示了當用 number
去索引 StringArray
時會獲得 string
類型的返回值。
Typescript
支持兩種索引簽名:字符串和數字。 能夠同時使用兩種類型的索引,可是數字索引的返回值必須是字符串索引返回值類型的子類型。
是由於當使用 number
來索引時,JavaScript
會將它轉換成 string
而後再去索引對象。 也就是說用 100
(一個number
)去索引等同於使用 "100"
(一個string
)去索引,所以二者須要保持一致。
class Animal {
name: string
}
class Dog extends Animal {
breed: string
}
// 錯誤:使用數值型的字符串索引,有時會獲得徹底不一樣的Animal
interface NotOkay {
[x: number]: Animal
[x: string]: Dog
}
複製代碼
下面的例子裏,name
的類型與字符串索引類型不匹配,因此類型檢查器給出一個錯誤提示
interface NumberDictionary {
[index: string]: number
length: number // 能夠,length是number類型
name: string // 錯誤,`name`的類型與索引類型返回值的類型不匹配
}
複製代碼
固然,咱們也能夠將索引簽名設置爲只讀,這樣就能夠防止給索引賦值
interface ReadonlyStringArray {
readonly [index: number]: string
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"]
myArray[2] = "Mallory" // error!
複製代碼
extends
即爲擴展、繼承。在 ts
中,extends
關鍵字既能夠來擴展已有的類型,也能夠對類型進行條件限定。在擴展已有類型時,不能夠進行類型衝突的覆蓋操做。例如,A爲 string
,在擴展出的類型中沒法將其改成 number
type num = {
num:number;
}
interface IStrNum extends num {
str:string;
}
// 與上面等價
type TStrNum = A & {
str:string;
}
複製代碼
在 ts
中,咱們還能夠經過條件類型進行一些三目操做:T extends U ? X : Y
type IsEqualType<A , B> = A extends B ? (B extends A ? true : false) : false
type NumberEqualsToString = IsEqualType<number,string> // false
type NumberEqualsToNumber = IsEqualType<number,number> // true
複製代碼
函數重載的基本語法:
declare function test(a: number): number
declare function test(a: string): string
const resS = test('Hello World') // resS 被推斷出類型爲 string
const resN = test(1234) // resN 被推斷出類型爲 number
複製代碼
這裏咱們申明瞭兩次?!爲何我不能判斷類型或者可選參數呢?後來我遇到這麼一個場景:
interface User {
name: string
age: number
}
declare function test(para: User | number, flag?: boolean): number 複製代碼
在這個 test 函數裏,咱們的本意多是當傳入參數 para 是 User 時,不傳 flag,當傳入 para 是 number 時,傳入 flag。TypeScript 並不知道這些,當你傳入 para 爲 User 時,flag 一樣容許你傳入:
const user = {
name: 'Jack',
age: 666
}
// 沒有報錯,可是與想法違背
const res = test(user, false);
複製代碼
使用函數重載能幫助咱們實現:
interface User {
name: string
age: number
}
declare function test(para: User): number declare function test(para: number, flag: boolean): number const user = { name: 'Jack', age: 666 };
// bingo
// Error: 參數不匹配
const res = test(user, false)
複製代碼
實際項目中,你可能要多寫幾步,如在 class 中:
interface User {
name: string
age: number
}
const user = {
name: 'Jack',
age: 123
};
class SomeClass {
/** * 註釋 1 */
public test(para: User): number
/** * 註釋 2 */
public test(para: number, flag: boolean): number
public test(para: User | number, flag?: boolean): number {
// 具體實現
return 11
}
}
const someClass = new SomeClass()
// ok
someClass.test(user)
someClass.test(123, false)
// Error
someClass.test(123)
someClass.test(user, false)
複製代碼
函數重載的意義在於可以讓你知道傳入不一樣的參數獲得不一樣的結果,若是傳入的參數不一樣,可是獲得的結果(類型)卻相同,那麼這裏就不要使用函數重載(沒有意義)
若是函數的返回值類型相同,那麼就不須要使用函數重載。
function func (a: number): number function func (a: number, b: number): number // 像這樣的是參數個數的區別,咱們可使用可選參數來代替函數重載的定義 function func (a: number, b?: number): number // 注意第二個參數在類型前邊多了一個`?` // 亦或是一些參數類型的區別致使的 function func (a: number): number function func (a: string): number // 這時咱們應該使用聯合類型來代替函數重載 function func (a: number | string): number 複製代碼
通常咱們都是先定義類型,再去賦值使用,可是使用 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.es5.d.ts
type Partial<T> = { [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 {
children: React.ReactNode
color: 'red' | 'blue' | 'yellow',
onClick (event: MouseEvent<HTMLDivElement>): void,
}
const Button: React.FC<Partial<IProps>> = ({onClick, children, color}) => {
return (
<div onClick={onClick}>
{ children }
</div>
)
複製代碼
Required
將全部 props
屬性都設爲必填項Required
實現源碼 node_modules/typescript/lib/lib.es5.d.ts
type Required<T> = { [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 | string> = T extends number ? Id : Name;
declare function createLabel<T extends number | string>(idOrName: T): T extends number ? Id : Name;
複製代碼
從 T
中排除那些能夠賦值給 U
的類型
Exclude
實現源碼 node_modules/typescript/lib/lib.es5.d.ts
type Exculde<T,U> = T extends U ? never : T;
複製代碼
實例:
type T = Exclude<1|2|3|4|5, 3|4> // T = 1|2|5
複製代碼
此時 T
類型的值只能夠爲 1
、2
、 3
,當使用其餘值是 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, U> = T extends U ? T : never;
複製代碼
實例:
type T = Extract<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<T, K extends keyof T> = {
[P in K]: T[P];
};
複製代碼
實例:
假如咱們如今有一個類型其擁有 name
、 age
、 sex
屬性,當咱們想生成一個新的類型只支持 name
、age
時能夠像下面這樣:
interface Person {
name: string,
age: number,
sex: string,
}
let person: Pick<Person, 'name' | 'age'> = {
name: '小王',
age: 21,
}
複製代碼
將 k
中全部的屬性的值轉化爲 T
類型。
Record
實現源碼 node_modules/typescript/lib/lib.es5.d.ts
type Record<K extends keyof any, T> = {
[P in K]: T;
};
複製代碼
實例:
將 name
、 age
屬性所有設爲 string
類型。
let person: Record<'name' | 'age', string> = {
name: '小王',
age: '12',
}
複製代碼
從對象 T
中排除 key
是 K
的屬性。
因爲 TS
中沒有內置,因此須要咱們使用 Pick
和 Exclude
進行實現。
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
複製代碼
實例:
排除 name
屬性。
interface Person {
name: string,
age: number,
sex: string,
}
let person: Omit<Person, 'name'> = {
age: 1,
sex: '男'
}
複製代碼
排除 T
爲 null
、undefined
NonNullable
實現源碼 node_modules/typescript/lib/lib.es5.d.ts
type NonNullable<T> = T extends null | undefined ? never : T;
複製代碼
實例:
type T = NonNullable<string | string[] | null | undefined>; // string | string[]
複製代碼
獲取函數 T
返回值的類型
ReturnType
實現源碼 node_modules/typescript/lib/lib.es5.d.ts
type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any;
複製代碼
infer R
至關於聲明一個變量,接收傳入函數的返回值類型。
實例:
type T1 = ReturnType<() => string>; // string
type T2 = ReturnType<(s: string) => void>; // void
複製代碼