以函数式组件为例,整理项目中用到的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
useMemo
和useCallback
接收的参数都是一样,都是在其依赖项发生变化后才执行,都是返回缓存的值,区别在于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