一、在 react 中使用 ts 的几点原则和变化

  • 所有用到jsx语法的文件都需要以tsx后缀命名
  • 使用组件声明时的Component泛型参数声明,来代替PropTypes!
  • 全局变量或者自定义的window对象属性,统一在项目根下的global.d.ts中进行声明定义
  • 对于项目中常用到的接口数据对象,在types/目录下定义好其结构化类型声明

二、类组件

1. Class Component

  • 只要在组件内部使用了props和state,就需要在声明组件时指明其类型。
    1. interface IProps {
    2. message: string;
    3. }
    4. interface IState {
    5. count: number;
    6. }
    7. export class MyComponent extends React.Component<IProps, IState> {
    8. state: IState = {
    9. count: 0
    10. };
    11. render() {
    12. return (
    13. <div>
    14. {this.props.message} {this.state.count}
    15. </div>
    16. );
    17. }
    18. }

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): void; // … } const MyComponent: React.FC = (props) => { const { children, …restProps } = props; return

{children}
; }

of

const MyComponent = (props:IProps) => { const { children, …restProps } = props; return

{children}
; }


<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 = (p) => { const props = mergeProps(defaultProps, p)

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属性

image.png

  • 泛型约束

image.png

  • 箭头函数泛型存在语法兼容问题需要特殊处理
  • 必须使用extends关键字来定义泛型参数才能被成功解析:

image.png

  • 修改后的 ```javascript //列表组件 interface Props { list: T[], renderItem(item: T, i: number): void }

const MapList = ({ list = [], renderItem }: Props) => { return (

{list.map(renderItem)}
); }

const foo: (x: T) => T = x => x;

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 (

.renderItem(item: List, i: number): void renderItem={(item) => (

{${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) { return { …state, …payload, } }

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((props, ref) => { const [value, setValue] = useState(‘’) const nativeInputRef = useRef(null)

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(null)

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'
  >