一开始在 React 里用 TS 我是拒绝的。能不能不要再爆红了 QAQ
但是当我用了半年 TS、前几天用 JS 起了个小项目的时候,才感觉没类型提示真的有点不习惯了。JS 不会给你提示,要一通 console.log 大法慢慢调试。而 TS 则会预先对项目静态编译,这样可以避免拼写错误问题,写好的接口类型也方便多人协作时快速了解变量结构。
而困扰我在 React 里写 TS 的最大问题,其实就是类型该如何写,包括 React 中出现的特定结构的类型、事件类型等等,以及这些类型应该怎么才算写得比较规范。
约等于翻译了一下参考文档,仅作个人写项目参考
React 中常见的类型
函数式组件
React.FC<Props>
|React.FunctionComponent<Props>
import { FC } from 'React';
const Component: FC<Props> => {}
class 式组件
React.Component<Props, State>
import { Component } from 'React';
class component extends Component<Props, State> => {}
高阶组件(HOC)
React.ComponentProps<typeof XXX>
import { Diff } from 'utility-types'
interface InjectedProps {
// ...
}
const withState = <WrappedProps extends InjectedProps>(
WrappedComponent: React.ComponentType<WrappedProps>
) => {
type HOCProps = Diff<
React.ComponentProps<typeof WrappedComponent>,
InjectedProps
> & {
// 填写这个HOC扩充的props
};
}
type HOCState = {
readonly state: string;
}
handleChangeState = () => {
this.setState({ state: 'new state' })
}
class HOC extends React.Component<HOCProps, State> {
render(){
const {...restProps} = this.props;
return
<WrappedComponent
{...(restProps as WrappedProps)}
state={state}
onChangeState={handleChangeState}
/>
}
}
return HOC
}
React 元素
React.ReactElement
|JSX.Element
const element: React.ReactElement = <a /> || <Component />;
React 节点
React.ReactNode
除了元素,还包括字符串、数字、布尔值、null、undefined 等
const node: React.ReactNode = <a /> || <Component /> || "string";
style 类型
React.CSSProperties
const style: React.CSSProperties = {
color: "red",
fontSize: "12px",
};
const element = <div style={style} />;
HTML 属性类型
React.XXXHTMLAttributes<HTMLXXXElement>
用于拓展 HTML 元素的属性
// 例如定制一个Button组件,该type可以保留button的原有属性
const Button:React.FC<Props & React.ButtonHTMLAttributes<HTMLButtonElement> = props=>{...}
<Button type="button" disabled={true} />
声明事件处理程序
React.ReactEventHandler<HTMLXXXElement>
const handleChange: React.ReactEventHandler<HTMLInputElement> = (e) => {
console.log(e.target.value);
};
<input onChange={handleChange} />;
具体事件类型
React.XXXEvent<HTMLXXXElement>
一些常见的事件示例:ChangeEvent
, FormEvent
, FocusEvent
, KeyboardEvent
, MouseEvent
, DragEvent
, PointerEvent
, WheelEvent
, TouchEvent
const handleChange: React.ChangeEvent<HTMLInputElement> = (e) => {
console.log(e.target.value);
};
<input onChange={handleChange} />;
常见的编写需求
定义默认 Props
Props 的 type 可以使用 Partial
,保证在使用组件时可以只传入部分 Props。
建议返回类型选择 JSX.Element
而非 React.FC
,因为 React.FC
在 props 有默认值时可能会不正常工作
type Props{
name: string;
age: number;
}
export const Component: React.FC<Props> = (props):JSX.Element => {
const { name, age } = props;
return <div>{name} {age}</div>
}
Component.defaultProps = {
name: 'name',
age: 0,
}
另外,在Typescript 配合 React 实践 发现一个有意思的利用高阶组件设置默认 props 的写法,供参考
export const withDefaultProps = <
P extends object,
DP extends Partial<P> = Partial<P>
>(
defaultProps: DP,
Cmp: ComponentType<P>,
) => {
// 提取出必须的属性
type RequiredProps = Omit<P, keyof DP>;
// 重新创建我们的属性定义,通过一个相交类型,将所有的原始属性标记成可选的,必选的属性标记成可选的
type Props = Partial<DP> & Required<RequiredProps>;
Cmp.defaultProps = defaultProps;
// 返回重新的定义的属性类型组件,通过将原始组件的类型检查关闭,然后再设置正确的属性类型
return (Cmp as ComponentType<any>) as ComponentType<Props>;
};
组件中将传入子组件
定义 props 的 type 时可以使用React.PropsWithChildren<{type}>
,如此组件默认会接收传入的子组件,而不用另行定义
type Type = {
name: string;
age: number;
};
type Props = React.PropsWithChildren<Type>;
export const Component: React.FC<Props> = (props) => {
const { children, ...rest } = props;
return <div {...rest}>{children}</div>;
};
使用 Context
- Context
定义并创建默认 props + 使用React.createContext
创建 Context
// 定义默认props类型
type Themes = {
dark: React.CSSProperties;
light: React.CSSProperties;
};
// 创建默认props
export const themes: Themes = {
dark: {
color: "black",
},
light: {
color: "white",
},
};
// 定义Context的接收类型,也即传递的全局变量类型
export type ThemeContextProps = {
theme: CSSProperties;
toggleTheme?: () => void;
};
// 创建context
const ThemeContext = React.createContext<ThemeContextProps>({
theme: themes.dark,
});
// 导出context
export default ThemeContext;
- Provider
定义全局变量
import ThemeContext, { themes } from './theme-context';
export const ThemeProvider: ReactElement = () => {
const [state,setState] = useState<CSSProperties>({theme: themes.light})
const toggleTheme = () =>
setState(
(state) =>
({
theme: state.theme === themes.light ?
themes.dark : themes.light,
}))
return (
<ThemeContext.Provider value={{theme,toggleTheme}}>
<Component/>
</ThemeContext.Provider>
)
}
- Consumer
子组件消费
export const Component:ReactElement = (props:Props) => {
return (
<ThemeContext.Consumer>
{
({theme,toggleTheme})=><div style={theme} onClick={toggleTheme}/>
}
</ThemeContext.Consumer>
)
}
参考: