一、在 react 中使用 ts 的几点原则和变化
- 所有用到jsx语法的文件都需要以tsx后缀命名
- 使用组件声明时的Component
泛型参数声明,来代替PropTypes!
- 全局变量或者自定义的window对象属性,统一在项目根下的global.d.ts中进行声明定义
- 对于项目中常用到的接口数据对象,在types/目录下定义好其结构化类型声明
二、类组件
1. Class Component
- 只要在组件内部使用了props和state,就需要在声明组件时指明其类型。
interface IProps {
message: string;
}
interface IState {
count: number;
}
export class MyComponent extends React.Component<IProps, IState> {
state: IState = {
count: 0
};
render() {
return (
<div>
{this.props.message} {this.state.count}
</div>
);
}
}
2. defaultProps
- 在使用react 使用 ts时,不能通过 Modal.defaultProps = {} 这样的形式来给props添加默认值
- 可以使用 static关键字 定义defaultProps ```javascript
export interface IProps { mag: string; onCancel?(): void; onOk?(): void; okText?: string cancelText?: string mask?: boolean }
class Modal extends Component
static defaultProps = {
okText: '确认',
cancelText: '取消',
mask: true
}
constructor(props: IProps) {
super(props)
this.state = {
visible: false,
show: false
}
}
… }
<a name="MkCBa"></a>
### 三、函数组件
<a name="BVm6r"></a>
#### 1. Function Component
```javascript
import React,{ useSate } from 'react';
interface IProps {
message: string;
}
export const MyComponent: React.FC<IProps> = props {
const [count, setCount] = useSate(1)
return (
<div>
{props.message} {count}
</div>
)
}
2. React.FC
IProps无需声明children属性的类型。React.FC会自动为props添加这个属性类型。
如果children期望一个render prop,或者期望其他特殊的值,那么你还是要自己给children声明类型,而不是使用默认的React.ReactNode。
- props无需做类型标注。 ```javascript import * as React from ‘react’;
interface IProps {
// 用户传入style的时候就能够获得类型检查和代码补全
style?: React.CSSProperties;
// 使用@types/react提供的事件类型定义,这里指定event.target的类型是HTMLButtonElement
onClick(event: React.MouseEvent
of
const MyComponent = (props:IProps) => { const { children, …restProps } = props; return
<a name="eEBQi"></a>
#### 3. 使用范型
```javascript
import React, {FC} from 'react'
import {Table} from 'antd'
import {ColumnProps} from 'antd/lib/table'
interface GlobalTableProp<T> {
columns: ColumnProps<T>[]
datasource: T[]
}
// 这个地方的T怎么办 该怎么写
const GlobalTable: FC<GlobalTableProp<T>> = ({columns, datasource}) => {
return (
<div>
<Table columns={columns} dataSource={datasource} />}
</div>
)
}
export default GlobalTable
# of
// 可以不用FC
interface GlobalTableProp<T> {
columns: ColumnProps<T>[]
datasource: T[]
}
const GlobalTable = <T>(props: GlobalTableProp<T>) => {
return (
<div>
<Table columns={columns} dataSource={datasource} />}
</div>
)
}
4. defaultProps
- 在Function组件中使用props默认值 可以通过ES6解构在赋默认值,不过觉得太麻烦了。
- 可以根据传入的props 和 预先定义的默认值 组合出一份新的props来使用。 ```javascript import assign from ‘lodash/assign’ import assignWith from ‘lodash/assignWith’ import isUndefined from ‘lodash/isUndefined’
export function mergeProps(a: A, b: B): B & A export function mergeProps(a: A, b: B, c: C): C & B & A export function mergeProps(…items: any[]) { function customizer(objValue: any, srcValue: any) { return isUndefined(srcValue) ? objValue : srcValue }
let ret = assign({}, items[0])
for (let i = 1; i < items.length; i++) {
ret = assignWith(ret, items[i], customizer)
}
return ret
}
export type ButtonProps = { block?: boolean className?: string onClick?(): void disabled?: boolean }
const defaultProps = { color: ‘default’, block: false, onClick: () => { }, disabled: false, }
const classPrefix = api_button
const Button: React.FC
return <button onClick={props.onClick}> </button>
}
<a name="GOFdB"></a>
#### 5. 泛型组件
- 在实现react组件时,有时候我们也想通过泛型来定义某个属性的类型。比如常见的列表组件,如果不使用泛型,我们通常这样写
```javascript
import React from "react";
//列表组件
interface Props {
list: any[], //因为我们并不知道传进来的数组类型,只能用any代替
renderItem: (item: any, i: number) => any
}
const MapList: React.FC<Props> = ({ list, renderItem }) => {
return (
<div>
{list.map(renderItem)}
</div>
);
}
interface List {
id: number
name: string
amount: number
}
//存放列表组件的容器,即父组件
const Demo1 = () => {
const list: List[] = [
{ id: 1, name: "张一", amount: 1000 },
{ id: 2, name: "张二", amount: 2000 },
{ id: 3, name: "张三", amount: 3000 },
];
return (
<div title="泛型组件">
<MapList
list={list}
// (property) Props.renderItem: (item: any, i: number) => any
// 当我们使用组件MapList的renderItem属性时就不会有任何ts提示和类型约束,这违背了我们使用ts的初心,
// 要么在使用renderItem的时候每次都自己再定义一下renderItem={(item: List) => (...)},比较麻烦,
// id11111 是错误的因为返回的any类型 ts就不会提示
// 这时候泛型就派上大用场
renderItem={(item) => (<p key={item.id+ item.id11111}>{`${item.id}、${item.name}有¥${item.amount.toFixed(2)}`}</p>)}
/>
</div>
);
};
- 使用泛型修改以上组件
- 已经使用泛型推断为什么还会报错 T上不存在map属性
- 泛型约束
- 箭头函数泛型存在语法兼容问题需要特殊处理
- 必须使用extends关键字来定义泛型参数才能被成功解析:
- 修改后的
```javascript
//列表组件
interface Props
{ list: T[], renderItem(item: T, i: number): void }
const MapList =
const foo:
interface List { id: number name: string amount: number }
//存放列表组件的容器,即父组件 const Demo1 = () => { const list: List[] = [ { id: 1, name: “张一”, amount: 1000 }, { id: 2, name: “张二”, amount: 2000 }, { id: 3, name: “张三”, amount: 3000 }, ]; return (
{${item.id}、${item.name}有¥${item.amount.toFixed(2)}
}
<a name="fd1PT"></a>
#### 6. Hooks
- hooks的声明可以不用设置类型他会自动断言
```javascript
import { useState } from 'react';
// ...
const [val, toggle] = useState(false);
// val 被推断为 boolean 类型
// toggle 只能处理 boolean 类型
// 相同的
const [val, toggle] = useState<boolean>(false);
- 为复杂数据类型hook定义声明
const[obj,setObj]=useState<{name:string;age:number;sex:string}>({name:'tom',age:15,sex:'男'}) 对象 const[arr,setArr]=useState<Array<{name:string;age:number}>>=([{name:'tom',age:15}])
7. useReducer
- useReducer 是一个用于状态管理的 hook api。是 useState 的替代方案
- 在组件内状态特别多的时多个 useState 使用起来比较麻烦,这个时候就可以考虑使用 useReducer。 ```javascript
type dataInfoType = { loading: boolean name: string age: number }
// Partial 是内置API,作用之前说过,就是将某个类型里的属性全部变为可选项 ?
// 这样我们在调用dispatch时就可以修改 dataInfoType类型中 任意约束好的属性进行修改也 不会丢失类型保护
function reducer(state: dataInfoType, payload: Partial
const dataInfo: dataInfoType = { loading: false, name: ‘’, age: 18, }
const Demo1 = () => {
const [state, stateDispatch] = useReducer(reducer, dataInfo)
stateDispatch({ name: '2' })
return (
<div>{state.name}</div>
);
};Ï
<a name="NL1Ly"></a>
#### 8. useImperativeHandle
- useImperativeHandle(ref, createHandle, [deps])
- 可以让你通过父组件传递ref来获取子组建暴露出来的值/方法
- 通过useImperativeHandle可以只暴露特定的操作
- 通过useImperativeHandle的Hook, 将父组件传入的ref和useImperativeHandle第二个参数返回的对象绑定到了一起
- 所以在父组件中, 调用inputRef.current时, 实际上是返回的对象
- 作用: 减少暴露给父组件获取的DOM元素属性, 只暴露给父组件需要用到的DOM方法
- 参数1: 父组件传递的ref属性
- 参数2: 返回一个对象, 以供给父组件中通过ref.current调用该对象中的方法
- 不做ref约束本身也没什么问题,不过就是把子组件的dom暴露给了 父组件, 父组件可以拿到DOM后进行任意的操作, 直接暴露给父组件带来的问题是某些情况的不可控
```javascript
import React, { useRef, forwardRef } from 'react'
// forwardRef 函数式组件默认不可以加 ref,它不像类组件那样有自己的实例。
// 这个 API 一般是函数式组件用来接收父组件传来的 ref。
// 通过 forwardRef 可以将 ref 转发给子组件,子组件拿到父组件创建的 ref,绑定到自己的某一个元素中。
const Input = forwardRef((props, ref) => {
return <input type="text" ref={ref} />
})
- useImperativeHandle使用总结: ```javascript import React, { useState, forwardRef, useReducer, useImperativeHandle, useEffect, useRef } from “react”;
export type Props = { className?: ‘’ }
export type InputRef = { setValue: (val: string) => void focus: () => void blur: () => void clear: () => void }
// 子组件
const Input = forwardRef
useImperativeHandle(ref, () => ({
setValue: (val: string) => {
setValue(val)
},
clear: () => {
setValue('')
},
focus: () => {
nativeInputRef.current?.focus()
},
blur: () => {
nativeInputRef.current?.blur()
},
}))
const inputChange = (e: any) => {
setValue(e.target.value)
}
return <input value={value} onChange={inputChange} ref={nativeInputRef} />
})
//父组件
const Demo1 = () => {
const ref = useRef
return (
<div>
<Input ref={ref} />
<br />
<button onClick={() => {
ref.current?.setValue('sssssss')
}}>设置input值</button>
<br />
<button onClick={() => {
ref.current?.clear()
}}>清空</button>
<br />
<button onClick={() => {
ref.current?.focus()
}}>focus</button>
<br />
<button onClick={() => {
ref.current?.blur()
}}>blur</button>
</div>
)
};
export default Demo1;
<a name="atZiT"></a>
#### 9. 复杂类型的接口定义
```javascript
// 交叉类型声明
export type ButtonProps = {
color?: 'default' | 'primary' | 'success' | 'warning' | 'danger'
fill?: 'solid' | 'outline' | 'none'
size?: 'mini' | 'small' | 'middle' | 'large'
block?: boolean
loading?: boolean
loadingText?: string
disabled?: boolean
onClick?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void
type?: 'submit' | 'reset' | 'button'
shape?: 'default' | 'rounded' | 'rectangular'
} & NativeProps<
| '--text-color'
| '--background-color'
| '--border-radius'
| '--border-width'
| '--border-style'
| '--border-color'
>
// 1. &: 在 TypeScript 中交叉类型是将多个类型合并为一个类型。通过 & 运算符可以将现有的多种类型叠加到一起成为一种类型
// 2. NativeProps 是一个自定义封装的通用泛型工具
// 3. | 联合类型声明中 表示取值可以为多种类型中的一种
// NativeProps 可以封装到我们自己项目中 规范 样式类参数声明
// (property) NativeProps<S extends string = never>.className?: string | undefined
export interface NativeProps<S extends string = never> {
className?: string
style?: CSSProperties & Partial<Record<S, string>>
tabIndex?: number
}
// 1. 泛型 S 约束必须是 string类型 | undefined
// 2. style 约束 CSSProperties | Partial<Record<S, string>>
// 3. Record 这样理解 Record<K,V> 创建一个 新类型key是 K,value是:V
// 4. Partial 将Record返回的新类型转换成可选属性也就是, NativeProps传递泛型参数是可以选择传递某一个
type NativeInputProps = React.DetailedHTMLProps<
React.InputHTMLAttributes<HTMLInputElement>,
HTMLInputElement
>
export type InputProps = Pick<
NativeInputProps,
| 'maxLength'
| 'minLength'
| 'autoComplete'
| 'pattern'
| 'inputMode'
| 'type'
| 'onFocus'
| 'onBlur'
| 'autoCapitalize'
| 'autoCorrect'
| 'onKeyDown'
| 'onKeyUp'
| 'onCompositionStart'
| 'onCompositionEnd'
> & {
value?: string
defaultValue?: string
onChange?: (val: string) => void
placeholder?: string
disabled?: boolean
readOnly?: boolean
clearable?: boolean
onClear?: () => void
id?: string
onEnterPress?: (e: React.KeyboardEvent<HTMLInputElement>) => void
enterKeyHint?:
| 'enter'
| 'done'
| 'go'
| 'next'
| 'previous'
| 'search'
| 'send'
min?: number
max?: number
} & NativeProps<
'--font-size' | '--color' | '--placeholder-color' | '--text-align'
>