React常用的TS注解

5,219 阅读7分钟

以函数式组件为例,整理项目中用到的TS写法。持续补充。。。

1、React.FC

FC在@types/react18之前为children提供了隐式的类型(ReactElement | null),即使你的Props注解并没有定义children,你仍然可以在参数里解构出它。如果出现类型不兼容问题,可以显示声明children?: React.ReactNode

要想在函数体内使用props必须显示的定义它,使用了这个之后,就不需要用PropsType声明了。

interface BaseButtonProps{
  disabled?: boolean;
  size?: ButtonSize;
}
export const Button: React.FC<ButtonProps> = (props) => {
  const {
    disabled,
    size,
    children,
  } = props
  ...
}

props的注解不用标记readOnly 。 因为在添加到泛型组件时,会自动添加ReadOnly

2、useState

自动推导,或者传入泛型

 //自动推导出user为string类型
const [user]=React.useState('a')

//传入泛型
interface DataSourceObject{
  value: string;
}
type DataSourceType<T = {}> =  T & DataSourceObject
const [suggestions, setSuggestions] = useState<DataSourceType[]>([])

初始状态为null时候,需要显示声明类型


type User = {name:string} 
//例子1
const [user, setUser] = React.useState<User | null>(null)
//例子2
const [user,setUser] = React.useState<User>({} as User)

3、useRef

// 如果可以的话尽量使用的类型具体点 
// 比如使用HTMLDivElement 就比HTMLElement好很多,比Element好更多 

function Foo(){ 
  const divRef = React.useRef<HTMLDivElement|null>(null); 
  return <div ref={divRef}>etc<div/> 
}

// 存储button dom
const buttonRef = React.useRef<HTMLButtonElement | null>(null);

// 存储a dom
const linkRef = React.useRef<HTMLLinkElement | null>(null);

4、useImperativeHandle, forwardRef

export interface MyInputHandles {
    focus:() => void;
}

const MyInput: RefForwardingComponent<MyInputHandles, MyInputProps> = (props, ref) => {
    const inputRef = useRef<HTMLInputElement>(null);
    useImperativeHandle(ref, () =>({
        focus: () => {
            if(inputRef.current) {
                inputRef.current.focus();
            }
        }
    }))
    return <Input {...props} ref={inputRef}>    
}

export default forwardRef(MyInput)

5、useContext

useContext接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 的 value prop 决定。语法如下所示:const value = useContext(MyContext);

import React, {useContext} from "react";

type SelectCallback= (selectIndex: string) => void

interface IMenuContext{
  index: string;
  onSelect?: SelectCallback;
  defaultOpenSubMenus?: string[];
}

export const MenuContext = createContext<IMenuContext>({ index: '0' })

const Demo3Child: React.FC<IProps> = () => {
     ...
    const context = useContext(MenuContext);
    return (
        <div>
            {context}
        </div>
    );
}

const Demo3: React.FC<IDemoProps> = () => {
    const passedContext: IMenuContext = {
        index: currentActive ? currentActive : '0',
        onSelect: handleClick,
        defaultOpenSubMenus,
     }
    return (
        <MyContext.Provider value={passedContext}>
            <Demo3Child />
        </MyContext.Provider>
    );
};

6、useCallback和useMemo

useMemouseCallback接收的参数都是一样,都是在其依赖项发生变化后才执行,都是返回缓存的值,区别在于useMemo返回的是函数运行的结果,useCallback返回的是函数。 可以传入泛型或自动推断。 useMemo 的泛型指定返回值类型,useCallback 的泛型指定参数类型

useCallback

function useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T;

// 自动推断 (value: number) => number
const multiply = React.useCallback((value: number) => value * multiplier, [
  multiplier,
])

//泛型
const handleChange = React.useCallback<
  React.ChangeEventHandler<HTMLInputElement>
>(evt => {
  console.log(evt.target.value)
}, [])

useMemo

function useMemo(factory: () => T, deps: DependencyList | undefined): T;

interface IClass{
  classId: number,
  className: string,
  type: number,
  isGradeLeader:number
}
...
const [classInfo, setClassInfo] = useState<IClass[]>([])
const adminiClasses: IClass[] = useMemo(() => classInfo.filter(item => item.type === 1), [classInfo])
const teachingClasses: IClass[] = useMemo(() => classInfo.filter(item => item.type === 2), [classInfo])
//自动推断useMemo的泛型 <IClass[]>

7、事件处理函数

7.1、不带event参数

const Test: React.FC = () => {
    const handleClick = () => {
        // 做一系列处理
    };
    return (
        <div>
            <button onClick={handleClick}>按钮</button>
        </div>
    );
};

7.2、带event参数

不做处理,直接写铁定报错

const Test: React.FC<{}> = () => {
    const handleClick: React.MouseEvent<HTMLButtonElement> = event => {
        // 做一系列处理
        event.preventDefault();
    };
    return (
        <div>
            <button onClick={handleClick}>按钮</button>
        </div>
    );
};
// onClick是MouseEventHandler类型
onClick?: MouseEventHandler<T> | undefined;

// 那MouseEventHandler<T>是个类型别名,泛型是Element类型
type MouseEventHandler<T = Element> = EventHandler<MouseEvent<T>;

//泛型Element是一个接口,通过继承该接口实现了很多其它接口
interface Element { }
interface HTMLElement extends Element { }
interface HTMLButtonElement extends HTMLElement { }
interface HTMLInputElement extends HTMLElement { }
//建议传入更为具体的element接口,

对于其他事件也一样

7.3、事件类型列表

  • AnimationEvent : css动画事件
  • ChangeEvent:<input><select><textarea>元素的change事件
  • ClipboardEvent: 复制,粘贴,剪切事件
  • CompositionEvent:由于用户间接输入文本而发生的事件(例如,根据浏览器和PC的设置,如果你想在美国键盘上输入日文,可能会出现一个带有额外字符的弹出窗口)
  • DragEvent:在设备上拖放和交互的事件
  • FocusEvent::元素获得焦点的事件
  • FormEvent:当表单元素得失焦点/value改变/表单提交的事件
  • InvalidEvent: 当输入的有效性限制失败时触发(例如<input type="number" max="10">,插入数字20)
  • KeyboardEvent: 键盘键入事件
  • MouseEvent: 鼠标移动事件
  • PointerEvent: 鼠标、笔/触控笔、触摸屏)的交互而发生的事件
  • TouchEvent: 用户与触摸设备交互而发生的事件
  • TransitionEvent: CSS Transition,浏览器支持度不高
  • UIEvent:鼠标、触摸和指针事件的基础事件。
  • WheelEvent:在鼠标滚轮或类似的输入设备上滚动
  • SyntheticEvent:所有上述事件的基础事件。是否应该在不确定事件类型时使用

因为InputEvent在各个浏览器支持度不一样,所以可以使用KeyboardEvent代替

8、普通函数

普通函数是通用的

  • 一个具体类型的输入输出函数;
// 参数输入为number类型,通过类型判断直接知道输出也为number
function testFun1 (count: number) {
    return count * 2;
}
  • 预先不确定一个函数的输入和输出的具体类型,在使用的时候再去指定特定类型
// 用泛型
function testFun2<T> (arg: T): T {
    return arg;
}
  • async函数,返回的为Promise对象,可以使用then方法添加回调函数,Promise是一个泛型函数,T泛型变量用于确定then方法时接收的第一个回调函数的参数类型。
// 用接口
interface IResponse<T> {
    result: T,
    status: string
};

// 除了用接口外,还可以用对象
// type IResponse<T> = {
//      result: T,
//    status: string
// };

async function testFun3(): Promise<IResponse<number>> {
    return {
        status: 'success',
        result: 10
    }
}
testFun3.then(res=>{console.log(res)})

9、 TS内置类型

  • Partial:使对象中的所有属性都是可选的
interface IUser { name: string age: number department: string }
经过 Partial 类型转化后得到

type optional = Partial<IUser>

// optional的结果如下
type optional = {
    name?: string | undefined;
    age?: number | undefined;
    department?: string | undefined;
}
  • Omit:生成一个新类型,该类型拥有 T 中除了 K 属性以外的所有属性
type Foo = { name: string age: number } 
type Bar = Omit<Foo, 'age'> 
// 相当于 type Bar = { name: string }
  • ConstructorParameters :类构造函数参数类型的元组
  • Exclude:将一个类型从另一个类型排除
  • Extract:选择可分配给另一类型的子类型
  • InstanceType:从一个新类构造函数中获得的实例类型
  • NonNullable:从类型中排除空和未定义
  • Parameters:函数的形参类型的元组
  • Readonly:将对象中的所有属性设置为只读
  • ReadonlyArray:创建给定类型的不可变数组
  • Pick:对象类型的一种子类型,包含其键的子集
  • Record:从键类型到值类型的映射
  • Required:使对象中的所有属性都是必需的
  • ReturnType:函数的返回类型 这里有一个博客写的很详细,可以参考:TS 里几个常用的内置工具类型

10、自定义hook

自定义 Hook 的返回值如果是数组类型,TS 会自动推导为 Union 类型,解决:const断言

function useLoading() {
    const [isLoading, setState] = React.useState(false)
    const load = (aPromise: Promise<any>) => {
        setState(true)
        return aPromise.then(() => setState(false))
    }
    return [isLoading, load] as const //这个hook把isLoading和一个函数返回出去
    // 实际需要: [boolean, typeof load] 类型
    // 而不是自动推导的:(boolean | typeof load)[]
}

//可以定义一个方法可以统一处理 tuple(元组) 返回值
function tuplify<T extends any[]>(...elements: T) {
  return elements
}

把上面例子 return [isLoading, load] as const   改为=>   return tuplify(isLoading, load)

11、React.ComponentProps

获得组件的props类型可以使用React.ComponentProps<typeof Button>

例如:子组件必须接受一个name参数,父组件要把它的props传入子组件,那么父组件要拿到子组件这个必要的类型来定义自己的Iprops。使用React.ComponentProps拿到子组件必要类型,再用交叉类型&,定义自己的Iprops。

通过查找类型减少 type 的非必要导出,如果需要提供复杂的 type,应当提取到作为公共 API 导出的文件中。

// counter.tsx
import * as React from 'react'
export type Props = {
  name: string
}
const Counter: React.FC<Props> = props => {
  return <></>
}
export default Counter

//app.tsx
import Counter from './d-tips1'
type PropsNew = React.ComponentProps<typeof Counter> & {
  age: number//这是app组件要的
}
const App: React.FC<PropsNew> = props => {
  return <Counter {...props} />
}

12、特定类型的React.children

React.Children 提供了用于处理 this.props.children 不透明数据结构的实用方法。当用在在父组件中规定其children只能是特性的组件时,就需要做一些特殊处理。例如:规定Menu组件里面只能是MenuItem或SubMenu,而不能是其他元素,可以这样写

//Menu.tsx
import { MenuItemProps } from './menuItem'
const renderChildren = () => {
    return React.Children.map(children, (child, index) => {
     // 限制Menu下面只能是MenuItem或SubMenu,不能是其它元素
      const childElement = child as React.FunctionComponentElement<MenuItemProps>
      const { displayName } = childElement.type
      if (displayName === 'MenuItem' || displayName === 'SubMenu') {
        return React.cloneElement(childElement, { index: index.toString() }) 
        //将index混入到实例中
      } else {
        console.error('Warning:Menu has a child which is not a MenuItem or SubMenu component')
      }
    })
}
...
//menuItem.tsx
export interface MenuItemProps{
  disabled?: boolean;
  index?: string;
  className?: string;
  style?: React.CSSProperties;
}
const MenuItem:React.FC<MenuItemProps>=(props)=>{
    return (
     <li>{children}</li>
    )
}
MenuItem.displayName='MenuItem'
export defult MenuItem