React常见写法

贴出一份Github上star数较多的教程,react的TS写法教程

React内置的一些类型

react本身已经有完善的TS声明文件了,所以里面有些可以拿来就用,不需要自己定义,这里说一些常见的。

  • React.FC<Props>,定义函数组件用。
  • React.Component<Props, State>,定义类组件用。
  • React.ReactNode,用来定义各种 react 节点,比如<div />, <MyComp />, this is a just a text, null, 0
  • React.CSSProperties,用来定义内联style的。
  • React.HTMLProps<HTMLXXXELEMENT>,获取内建 html 元素的属性。
  • React.PropsWithChildren,如果需要显示使用 children,使用该类型创建 props,如:React.PropsWithChildren<{a: string}>
  • React.ReactEventHandler<HTMLXXXELEMENT>,用来描述元素的事件处理函数。可以自行推导出适合的事件函数描述。
  • React.ChangeEventHandler<HTMLXXXELEMENT>,最常见的如 input 的 change 事件函数描述,人为的判定类型。区别于上面的,这里的 event 可用 event.target.value,上面的是 event.currentTarget.value。类似还有MouseEventHandler, MouseEventHandler等。

React的常见写法

组件写法

类写法

  1. interface IProps {
  2. name: string;
  3. }
  4. interface IState {
  5. age: number;
  6. }
  7. // 这里接收2个类型参数,分别限定组件的props和state
  8. class ClsComp extends React.Component<IProps, IState> {
  9. state = {
  10. age: 18
  11. }
  12. }

函数写法

  1. // 函数组件无自己的状态,因此只需要props
  2. const FcComp: React.FC<{ prop1: string }> = ({ prop1 }) => {
  3. return <div>{prop1}</div>
  4. }

默认props写法

组件的props我们经常会写一些默认值,省去一些体力活。

类组件形式写法

  1. interface IProps extends defaultProps {
  2. age: number;
  3. }
  4. // 使用typeof获取到类组件的默认参数类型。这里就是{name: string}
  5. type defaultProps = typeof DefaultPropsComp.defaultProps
  6. class DefaultPropsComp extends React.Component<IProps> {
  7. static defaultProps = {
  8. name: 'jack'
  9. }
  10. echoProps = () => {
  11. // 这里也可以获得TS提示
  12. const { age, name } = this.props
  13. }
  14. render() {
  15. return <p>this is how to set default props of class component</p>
  16. }
  17. }

函数组件形式写法

  1. const defaultProps = {
  2. work: 'doctor'
  3. }
  4. interface IProps extends defaultPropsType {
  5. age: number;
  6. }
  7. type defaultPropsType = typeof defaultProps
  8. function DefaultPropsFC(props: IProps) {
  9. // 这里可以获得提示
  10. const { work, age } = props
  11. return <p>{`The ${work} is ${age}`}</p>
  12. }
  13. // 注意:一定要挂载默认属性,否则调用组件的地方默认值也需要传入
  14. DefaultPropsFC.defaultProps = defaultProps

常见的如style等写法

  1. /* 组件的定义 */
  2. interface IProps extends DefaultProps {
  3. descText: string // 基本的类型
  4. style?: React.CSSProperties // 可以在调用组件的地方,获得style的完美提示。
  5. additionDom?: React.ReactNode // 接收任何类型的React节点
  6. renderReactNode?: () => React.ReactNode // 接收一个以函数方式返回的React节点
  7. onChange: React.FormEventHandler<HTMLInputElement> // 优先推荐这种写法,让TS自己推断。下面的方法定义同样可以。
  8. // onChange: (event: React.ChangeEvent<HTMLInputElement>) => void
  9. buttonSize: 'small' | 'middle' | 'big' // 可选参数
  10. }
  11. type DefaultProps = typeof UsefulReactProp.defaultProps
  12. interface IState {
  13. value: string
  14. }
  15. export default class UsefulReactProp extends React.Component<IProps, IState> {
  16. static defaultProps = {
  17. initVal: '输入内容'
  18. }
  19. // 定义一个dom引用。使用如下定义方式
  20. static aDom = React.createRef<HTMLDivElement>()
  21. // 定义一个类属性,可以在需要时在赋值使用
  22. static classProp?: string
  23. // 定义一个类属性,在定义时即复制
  24. static classProp2 = 'hello'
  25. state = {
  26. value: this.props.initVal
  27. }
  28. defaultOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  29. const { onChange } = this.props
  30. this.setState({
  31. value: e.target.value
  32. })
  33. this.classProp = 'hello world'
  34. onChange(e)
  35. }
  36. render() {
  37. const { onChange, style, additionDom, renderReactNode } = this.props
  38. const { value } = this.state
  39. // 如下代码提示只有3种上述定义过的值。
  40. const btnSize =
  41. this.props.buttonSize === 'big'
  42. ? '100px'
  43. : this.props.buttonSize === 'small'
  44. ? '60px'
  45. : '80px;'
  46. return (
  47. <div>
  48. <input type="text" value={value} onChange={this.defaultOnChange} />
  49. <p className="desc" style={style}>
  50. 这是个带样式的提醒
  51. </p>
  52. <div className="additional">{additionDom}</div>
  53. <div className="by-func-rcnode">
  54. {renderReactNode ? renderReactNode() : null}
  55. </div>
  56. <button style={{ width: btnSize }}>按钮尺寸:联合类型提示</button>
  57. </div>
  58. )
  59. }
  60. }
  61. /* 组件的调用 */
  62. // 书写可以获得提示。
  63. <UsefulReactProp
  64. descText="一个简单的描述"
  65. onChange={e => {
  66. console.log(e.currentTarget.value)
  67. }}
  68. style={{ fontSize: 14, fontWeight: 'bold' }}
  69. additionDom={<p>this is additional tag</p>}
  70. renderReactNode={() => {
  71. return <p>这是通过函数返回的ReactNode</p>
  72. }}
  73. buttonSize="big"
  74. />

React中断言

断言,即认定,确定,以及肯定。当一个值可能是多种类型,不同类型又有不同方法时,我们调用时就会出现方法不存在另外类型上,报错,这个时候就需要断言,认定一个值是我需要的类型,按照认定的类型来使用。React中的断言用 as 语法,而不是 <string>a 语法,因为尖括号语法跟JSX语法有冲突。

  1. type UserA = {
  2. name: string,
  3. age: number
  4. }
  5. type UserB = {
  6. name: string,
  7. sex: string
  8. }
  9. type IProps = {
  10. user: UserA | UserB
  11. }
  12. class AssertComp extends React.Component<IProps, {}> {
  13. render() {
  14. // 这里如果不做类型断言,会提示UserA上没有sex属性
  15. // const user = this.props.user
  16. const userB = this.props.user as UserB
  17. const userA = this.props.user as UserA
  18. return <div>
  19. <p>this is a user: {userA.age}</p>
  20. <p>this is a user: {userB.sex}</p>
  21. </div>
  22. }
  23. }

Context写法

Context是React的一个特性,可以避免在嵌套组件中一层层传递props。常用的Redux库中的 Provider 组件就是利用了Context特性。
这里使用 React.createContext 类型实现。

  1. /*
  2. ctx.tsx
  3. 该文件,定义了我们需要的context对象
  4. */
  5. export type Theme = React.CSSProperties
  6. type Themes = {
  7. dark: Theme
  8. light: Theme
  9. }
  10. export const themes: Themes = {
  11. dark: {
  12. color: 'black',
  13. backgroundColor: 'white'
  14. },
  15. light: {
  16. color: 'white',
  17. backgroundColor: 'black'
  18. }
  19. }
  20. // 这里定义Context的签名
  21. export type ThemeContextProps = { theme: Theme; toggleTheme?: () => void }
  22. // createContext<T>需要一个类型,把定义好的Context签名传递进去
  23. const ThemeContext = React.createContext<ThemeContextProps>({
  24. theme: themes.light
  25. })
  26. /*
  27. themeButton.tsx
  28. 最终的消费组件(消费上面定义的context对象)
  29. */
  30. import ThemeContext from './ctx'
  31. // contextType v16.6+支持
  32. export default class ToggleThemeButton extends React.Component<{}> {
  33. static contextType = ThemeContext
  34. // 后面需要通过this.context访问到上下文,所以定义一个类属性
  35. // 这里使用!明确告诉TS,该参数一定有。
  36. // 这里不能使用?,因为context是必选项
  37. context!: React.ContextType<typeof ThemeContext>
  38. render() {
  39. // 前面context必须这么写,这里才能获取TS提示
  40. const { theme, toggleTheme } = this.context
  41. return (
  42. <button style={theme} onClick={toggleTheme} {...this.props}>
  43. button
  44. </button>
  45. )
  46. }
  47. }
  48. /*
  49. provider组件
  50. context对象的提供组件,相对于消费组件来说的。
  51. */
  52. import ThemeContext, { Theme, themes } from './ctx'
  53. import ThemeButton from './themeButton'
  54. export default class ThemeProvider extends React.Component<{}, State> {
  55. state: State = {
  56. theme: themes.light
  57. }
  58. toggleTheme = () => {
  59. this.setState(state => ({
  60. theme: state.theme === themes.light ? themes.dark : themes.light
  61. }))
  62. }
  63. render() {
  64. const { theme } = this.state
  65. const { toggleTheme } = this
  66. return (
  67. <ThemeContext.Provider value={{ theme, toggleTheme }}>
  68. <ThemeButton />
  69. </ThemeContext.Provider>
  70. )
  71. }
  72. }

高阶组件HOC

先理解高阶函数,一个接收函数P作为参数,并且返回一个新的函数N的函数F,这个F即叫做高阶函数。同理,高阶组件,一个接收组件P作为参数,并且返回一个新的组件N的组件F,这个F叫做高阶组件。常见的如React-Redux库中的 connect 方法

这里写一个例子,一个 withState 函数,接收一个没有 count 状态的组件,包装后,返回一个带有 count 状态的组件。

  1. /* withState.tsx */
  2. export interface InjectedProps {
  3. count: number,
  4. onIncrement: () => void
  5. }
  6. export const withState = <IProp extends InjectedProps>(WrappedComp: React.ComponentType<IProp>) => {
  7. // 使用Omit结合keyof,实现排除掉IProp和InjectedProps交集的部分(count, onIncrement)
  8. type HocProps = Omit<IProp, keyof InjectedProps> & {
  9. // 这里可以做HOC的Props的扩展。
  10. initialCount?: number
  11. }
  12. type HocState = { readonly count: number }
  13. return class Hoc extends React.Component<HocProps, HocState> {
  14. // 组件的名字,方便调试
  15. static displayName = `withState(${WrappedComp.name})`
  16. state: HocState = {
  17. count: Number(this.props.initialCount) || 0
  18. }
  19. handleInc = () => {
  20. this.setState({
  21. count: this.state.count + 1
  22. })
  23. }
  24. render() {
  25. const { ...restProps } = this.props
  26. const { count } = this.state
  27. return (
  28. <WrappedComp
  29. count={count}
  30. onIncrement={this.handleInc}
  31. // HOC组件,应该透传与HOC无关的Prop给被包裹的组件。这里的写法是因为TS本身的BUG,如果不断言,则类型报错。
  32. {...(restProps as IProp)}
  33. ></WrappedComp>
  34. )
  35. }
  36. }
  37. }
  38. /*
  39. FcCounter.tsx
  40. 暴露一个函数组件,函数组件没有自己的状态。
  41. */
  42. export const FcCounter: React.FC<Props> = props => {
  43. const { label, count, onIncrement } = props
  44. const handleIncrement = () => {
  45. onIncrement()
  46. }
  47. return (
  48. <div>
  49. <span>
  50. {label}:{count}
  51. </span>
  52. <button type="button" onClick={handleIncrement}>{`Increment`}</button>
  53. </div>
  54. )
  55. }
  56. /* 调用组件 */
  57. import { FcCounter } from './FcCounter'
  58. import { withState } from './withState'
  59. const HocCounterWithState = withState(FcCounter)
  60. <HocCounterWithState initialCount={10} label="HocCounterWithState" />

通用组件写法

通用组件复用性较高,如 <List> 这种类型的,只是个列表容器,具体的列表每一项自定义。主要思想就是利用泛型。

  1. // 组件定义
  2. interface IProps<T> {
  3. lists: T[]
  4. renderItem: (item: T) => React.ReactNode // 最好都用ReactNode,而不是JSX.Element。
  5. }
  6. class List<T> extends React.Component<IProps<T>, {}> {
  7. render() {
  8. const {lists, renderItem} = this.props
  9. return (
  10. <>
  11. <ul>{lists.map(renderItem)}</ul>
  12. </>
  13. )
  14. }
  15. }
  16. // 调用组件。注意:这里List传入了一个类型number,限制了泛型T的类型。
  17. <List<number> lists={[1, 2, 3]} renderItem={item => <li key={item}>{item}</li>}/>

useRef+useImperativeHandle+forwardRef写法

函数组件没有实例,如何在父组件拿到函数子组件的方法呢?

  1. // 父组件
  2. import { useRef } from 'react';
  3. import Filter from './components/Filter';
  4. type FilterHandle = React.ElementRef<typeof Filter> // *
  5. function Parent() {
  6. const FilterRef = useRef<FilterHandle>(null) // *
  7. return <Filter ref={FilterRef} /> // *
  8. }
  9. // 子组件
  10. import { useImperativeHandle, forwardRef } from 'react';
  11. import {Form} from 'antd'
  12. // 自定义返回的函数组件方法
  13. type FilterHandle = {
  14. getFieldsValue: () => {[key: string]: any}
  15. }
  16. type FilterProps = {}
  17. // TS定义下是一个ForwardRef组件
  18. const Filter: React.ForwardRefRenderFunction<FilterHandle, FilterProps> = (props, ref) => {
  19. const [form] = Form.useForm();
  20. // 生成返回给父组件的自定义实例值
  21. useImperativeHandle(ref, () => ({
  22. getFieldsValue() {
  23. // 可以暴露出form的一些方法
  24. return form.getFieldsValue()
  25. }
  26. }));
  27. return (
  28. <Form className={styles.wrap} form={form}>
  29. <Row gutter={12}>
  30. <Col span={6}>
  31. <Form.Item label="账号名称" name="name">
  32. <Input placeholder="请输入账号名称" />
  33. </Form.Item>
  34. </Col>
  35. <Col span={18} style={{ textAlign: 'right' }}>
  36. <Form.Item>
  37. <Space size={16}>
  38. <Button>重置</Button>
  39. <Button type="primary">查询</Button>
  40. </Space>
  41. </Form.Item>
  42. </Col>
  43. </Row>
  44. </Form>
  45. );
  46. }
  47. export default forwardRef(Filter);