但是当我用了半年 TS、前几天用 JS 起了个小项目的时候,才感觉没类型提示真的有点不习惯了。JS 不会给你提示,要一通 console.log 大法慢慢调试。而 TS 则会预先对项目静态编译,这样可以避免拼写错误问题,写好的接口类型也方便多人协作时快速了解变量结构。
而困扰我在 React 里写 TS 的最大问题,其实就是类型该如何写,包括 React 中出现的特定结构的类型、事件类型等等,以及这些类型应该怎么才算写得比较规范。
约等于翻译了一下参考文档,仅作个人写项目参考
React 中常见的类型
函数式组件
React.FC<Props>|React.FunctionComponent<Props>
typescriptimport { FC } from 'React'; const Component: FC<Props> => {}
class 式组件
React.Component<Props, State>
typescriptimport { Component } from 'React'; class component extends Component<Props, State> => {}
高阶组件(HOC)
React.ComponentProps<typeof XXX>
typescriptimport { 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
typescriptconst element: React.ReactElement = <a /> || <Component />;
React 节点
React.ReactNode
除了元素,还包括字符串、数字、布尔值、null、undefined 等
typescriptconst node: React.ReactNode = <a /> || <Component /> || "string";
style 类型
React.CSSProperties
typescriptconst style: React.CSSProperties = { color: "red", fontSize: "12px", }; const element = <div style={style} />;
HTML 属性类型
React.XXXHTMLAttributes<HTMLXXXElement>
用于拓展 HTML 元素的属性
typescript// 例如定制一个Button组件,该type可以保留button的原有属性 const Button:React.FC<Props & React.ButtonHTMLAttributes<HTMLButtonElement> = props=>{...} <Button type="button" disabled={true} />
声明事件处理程序
React.ReactEventHandler<HTMLXXXElement>
typescriptconst 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
typescriptconst 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 有默认值时可能会不正常工作
typescripttype 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 的写法,供参考
TypeScriptexport 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}>,如此组件默认会接收传入的子组件,而不用另行定义
typescripttype 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
typescript// 定义默认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
定义全局变量
TypeScriptimport 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
子组件消费
TypeScriptexport const Component:ReactElement = (props:Props) => { return ( <ThemeContext.Consumer> { ({theme,toggleTheme})=><div style={theme} onClick={toggleTheme}/> } </ThemeContext.Consumer> ) }
参考:
~~(完全参考)~~react-redux-typescript-guide