作者:花生毛豆-
来源:CSDN
原文:https://blog.csdn.net/s2096828/article/details/83744677
版权声明:本文为博主原创文章,转载请附上博文链接!
编写第一个 TSX 组件
import React from 'react'
import ReactDOM from 'react-dom'
const App = () => {
return (<div>Hello world</div>
)
}
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 安装
yarn add @types/react
yarn add @types/react-dom
使用 npm 安装
npm i @types/react -s
npm i @types/react-dom -s
有状态组件开发
我们定义一个 App 有状态组件,props
、 state
如下。
Props
props | 类型 | 是否必传 |
---|---|---|
color |
string | 是 |
size |
string | 否 |
State
props | 类型 |
---|---|
count |
string |
使用 TSX 我们可以这样写
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
的类型定义。定义后在使用 this.state
和 this.props
时可以在编辑器中获得更好的智能提示,并且会对类型进行检查。
那么 Component
的泛型是如何实现的呢,我们可以参考下 React
的类型定义文件 node_modules/@types/react/index.d.ts
。
在这里可以看到 Component
这个泛型类, P
代表 Props 的类型, S
代表 State
的类型。
class Component<p, s=""> {
readonly props: Readonly<{ children?: ReactNode }> & Readonly<p>
state: Readonly<s>
}
现在有了 TypeScript
我们可以通过将 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 = {
count: 1,
}
public render () {
return (
</s></p><div><s>Hello world</s></div><s><s>
)
}
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
props | 类型 | 是否必传 |
---|---|---|
children | ReactNode | 否 |
onClick | function | 是 |
SFC类型
在 React 的声明文件中 已经定义了一个 SFC 类型,使用这个类型可以避免我们重复定义 children、 propTypes、 contextTypes、 defaultProps、displayName 的类型。
实现源码 node_modules/@types/react/index.d.ts 。
type SFC</s></s><p> = StatelessComponent</p><p>
interface StatelessComponent</p><p> {
(props: P & { children?: ReactNode }, context?: any): ReactElement | null;
propTypes?: ValidationMap</p><p>
contextTypes?: ValidationMap;
defaultProps?: Partial</p><p>
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 (
</p><div>
{ children }
</div><s><s>
)
}
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 Change
事件对象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
继承 SyntheticEvent
,并且通过 T
接收一个 DOM
元素的类型, currentTarget
的类型由 EventTarget & T
组成交叉类型。
事件处理函数类型
当我们定义事件处理函数时有没有更方便定义其函数类型的方式呢?答案是使用 React
声明文件所提供的 EventHandler
类型别名,通过不同事件的 EventHandler
的类型别名来定义事件处理函数的类型。
EventHandler
类型实现源码 node_modules/@types/react/index.d.ts
。
type EventHandler> = { bivarianceHack(event: E): void }["bivarianceHack"];
type ReactEventHandler = EventHandler<syntheticevent>
type ClipboardEventHandler = EventHandler<clipboardevent>
type DragEventHandler = EventHandler<dragevent>
type FocusEventHandler = EventHandler<focusevent>
type FormEventHandler = EventHandler<formevent>
type ChangeEventHandler = EventHandler<changeevent>
type KeyboardEventHandler = EventHandler<keyboardevent>
type MouseEventHandler = EventHandler<mouseevent>
type TouchEventHandler = EventHandler<touchevent>
type PointerEventHandler = EventHandler<pointerevent>
type UIEventHandler = EventHandler<uievent>
type WheelEventHandler = EventHandler<wheelevent>
type AnimationEventHandler = EventHandler<animationevent>
type TransitionEventHandler = EventHandler<transitionevent>
EventHandler
接收 E
,其代表事件处理函数中 event
对象的类型。
bivarianceHack
为事件处理函数的类型定义,函数接收一个 event
对象,并且其类型为接收到的泛型变量 E
的类型, 返回值为 void
。
实例:
interface IProps {
onClick : MouseEventHandler,
}
Promise 类型
在做异步操作时我们经常使用 async
函数,函数调用时会 return
一个 Promise
对象,可以使用 then
方法添加回调函数。
Promise
是一个泛型类型,T
泛型变量用于确定使用 then
方法时接收的第一个回调函数(onfulfilled
)的参数类型。
实例:
interface IResponse {
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
实现源码 node_modules/typescript/lib/lib.es5.d.ts
。
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
一般我们都是先定义类型,再去赋值使用,但是使用 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
将所有的 props
属性都变为可选值
Partial
实现源码 node_modules/typescript/lib/lib.es5.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<partial> = ({onClick, children, color}) => {
return (</s></s><div>
{ children }
</div><s>
)
使用 Required
将所有 props
属性都设为必填项
Required
实现源码 node_modules/typescript/lib/lib.es5.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;
Exclude<t,u>
从 T
中排除那些可以赋值给 U
的类型。
Exclude
实现源码 node_modules/typescript/lib/lib.es5.d.ts
。
type Exclude<t, u=""> = 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'
.
Extract<t,u>
从 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'
.
Pick<t,k>
从 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,
}
Record<k,t>
将 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',
}
Omit<t,k>
(没有内置)
从对象 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: '男'
}
NonNullable
排除 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[]
ReturnType
获取函数 T
返回值的类型。。
ReturnType
实现源码 node_modules/typescript/lib/lib.es5.d.ts
。
type ReturnType any> = T extends (...args: any[]) => infer R ? R : any;
infer R
相当于声明一个变量,接收传入函数的返回值类型。
实例:
type T1 = ReturnType<() => string> // string
type T2 = ReturnType<(s: string) => void> // void
No Leanote account? Sign up now.