Typescript 与 React 结合使用的初学者指南

2025-05-27

Typescript 与 React 结合使用的初学者指南

过去几个月我一直在使用 Typescript 开发 React 应用程序和库,想分享一些我一路走来学到的东西。这些是我在 Typescript 和 React 中大约 80% 时间使用的模式。

学习 Typescript 来开发 React 应用值得吗?绝对值得。我发现,强大的类型功能可以带来更可靠的代码和更快的迭代速度,尤其是在代码库规模较大的情况下。一开始你可能会感到沮丧,但随着你逐渐熟悉,你会发现,这些额外的样板代码非常值得。

如果你遇到困难,请记住你随时可以输入any。Any 是你的朋友!

让我们来看一些例子。

使用 TypeScript 的基本 React 组件

那么,使用 TypeScript 编写的标准 React 组件是什么样的呢?让我们将它与标准的 JavaScript React 组件进行比较。

import React from 'react'
import PropTypes from 'prop-types'

export function StandardComponent({ children, title = 'Dr.' }) {
  return (
    <div>
      {title}: {children}
    </div>
  )
}

StandardComponent.propTypes = {
  title: PropTypes.string,
  children: PropTypes.node.isRequired,
}

现在是打字稿版本:

import React, { ReactNode } from 'react'

export type StandardComponentProps = {
  title?: string;
  children: ReactNode;
}

export function StandardComponent({
  children,
  title = 'Dr.',
}: StandardComponentProps) {
  return (
    <div>
      {title}: {children}
    </div>
  )
}

很像吧?我们用 TypeScript 类型替换了 propTypes。title 属性仍然是可选的,而 children 属性是必需的。我们导出了该类型,以便其他组件需要引用它。

扩展标准 HTML 属性

如果我们希望父组件能够提供额外的类型div属性,例如aria-hiddenstyleclassName我们可以在组件中定义这些属性type,也可以扩展内置类型。在下面的例子中,我们表示我们的组件div除了title和 之外,还接受任何标准 props children

import * as React from 'react'

export type SpreadingExampleProps = {
  title?: string;
  children: React.ReactNode;
} & React.HTMLAttributes<HTMLDivElement>;

export function SpreadingExample({
  children,
  title = 'Dr.',
  ...other
}: SpreadingExampleProps) {
  return (
    <div {...other}>
      {title}: {children}
    </div>
  )
}

处理事件

我们可以指定事件处理程序的类型,以确保事件参数的类型正确。以下示例演示了实现此目的的多种方法:

export type EventHandlerProps = {
  onClick: (e: React.MouseEvent) => void;
}

export function EventHandler({ onClick }: EventHandlerProps) {
  // handle focus events in a separate function
  function onFocus(e: React.FocusEvent) {
    console.log('Focused!', e.currentTarget)
  }

  return (
    <button
      onClick={onClick}
      onFocus={onFocus}
      onKeyDown={e => {
        // When using an inline function, the appropriate argument signature
        // is provided for us
      }}
    >
      Click me!
    </button>
  )
}

不确定要使用哪个参数签名?在编辑器中,尝试将光标悬停在相关的事件处理程序属性上。

使用字符串文字

您是否有一个 prop,需要一个与一组预定义选项匹配的字符串?您可以使用 TypeScript 的字符串字面量来实现。

type Props = {
  title: "senior" | "junior";
}

function Label({ title }: Props) {
  return <div>Title: {title}</div>
}

现在,如果标题不是seniorjunior,typescript 就会对你大喊大叫。

在 React 组件中使用泛型

这是一个更高级的功能,但功能非常强大。通常,你会在 React 组件中定义数据类型及其特定属性。假设你的组件需要一个配置文件对象。

type ProfileType = {
  name: string;
  image: string;
  age: number | null;
}

type ProfilesProps = {
  profiles: Array<ProfileType>;
}

function Profiles(props: ProfilesProps) {
  // render a set of profiles
}

但现在让我们假设你有一个组件,它可以接受任何类型的数组。泛型类似于邮寄包裹。快递员(我们的组件)不需要知道你发送的包裹的具体内容,但发件人(父组件)希望收件人收到他们发送的内容。

以下是我们的操作方法:

type GenericsExampleProps<T> = {
  children: (item: T) => React.ReactNode;
  items: Array<T>;
}

export function GenericsExample<T>({
  items,
  children,
}: GenericsExampleProps<T>) {
  return (
    <div>
      {items.map(item => {
        return children(item)
      })}
    </div>
  )
}

有点奇怪的例子……但它说明了要点。该组件接受任意类型的项目数组,遍历该数组并使用项目对象调用 children 作为渲染函数。当我们的父组件作为子组件提供渲染回调时,其item类型将被正确显示!

没明白?没关系。我也不完全理解泛型,不过你不太可能经常用到它。而且你用 TypeScript 越多,就越能理解它的意义。

打字钩子

Hooks 大部分情况下都是开箱即用的。以下两个例外是:有时useRefuseReducer。下面的示例演示了如何键入 refs。

import * as React from 'react'

type HooksExampleProps = {}

export function HooksExample(props: HooksExampleProps) {
  const [count, setCount] = React.useState(0)
  const ref = React.useRef<HTMLDivElement | null>(null)

  // start our timer
  React.useEffect(
    () => {
      const timer = setInterval(() => {
        setCount(count + 1)
      }, 1000)

      return () => clearTimeout(timer)
    },
    [count]
  )

  // measure our element
  React.useEffect(
    () => {
      if (ref.current) {
        console.log(ref.current.getBoundingClientRect())
      }
    },
    [ref]
  )

  return <div ref={ref}>Count: {count}</div>
}

我们的状态是自动输入的,但我们手动输入了 our ,ref以表明它要么为空,要么包含一个div元素。当我们在函数中访问我们的 ref 时useEffect,我们需要确保它不为空。

输入 reducer

减速器稍微复杂一点,但是如果类型正确的话确实很好。

// Yeah, I don't understand this either. But it gives us nice typing
// for our reducer actions.
type Action<K, V = void> = V extends void ? { type: K } : { type: K } & V

// our search response type
type Response = {
  id: number;
  title: string;
}

// reducer actions. These are what you'll "dispatch"
export type ActionType =
  | Action<'QUERY', { value: string }>
  | Action<'SEARCH', { value: Array<Response> }>

// the form that our reducer state takes
type StateType = {
  searchResponse: Array<Response>;
  query: string;
}

// our default state
const initialState: StateType = {
  searchResponse: [];
  query: '';
}

// the actual reducer
function reducer(state: StateType, action: ActionType) {
  switch (action.type) {
    case 'QUERY':
      return {
        ...state,
        query: action.value,
      }

    case 'SEARCH':
      return {
        ...state,
        searchResponse: action.value,
      }
  }
}

type ReducerExampleProps = {
  query: string;
}

export function ReducerExample({ query }: ReducerExampleProps) {
  const [state, dispatch] = React.useReducer(reducer, initialState)

  React.useEffect(
    () => {
      if (query) {
        // emulate async query
        setTimeout(() => {
          dispatch({
            type: 'SEARCH',
            value: [{ id: 1, title: 'Hello world' }],
          })
        }, 1000)
      }
    },
    [query]
  )

  return state.searchResponse.map(response => (
    <div key={response.id}>{response.title}</div>
  ))
}

使用typeofkeyof键入组件变量

假设我们想要构建一个具有多种外观的按钮,每个按钮都在一个具有一组键和样式的对象中定义,如下所示:

const styles = {
  primary: {
    color: 'blue',
  },
  danger: {
    color: 'red',
  },
}

我们的按钮组件应该接受一个typeprop,该 prop 可以是对象的任意键styles(例如,“primary”或“danger”)。我们可以很容易地输入以下内容:

const styles = {
  primary: {
    color: 'blue',
  },
  danger: {
    color: 'red',
  },
}

// creates a reusable type from the styles object
type StylesType = typeof styles

// ButtonType = any key in styles
export type ButtonType = keyof StylesType

type ButtonProps = {
  type: ButtonType
}

export function Button({ type = 'primary' }: ButtonProps) {
  return <button style={styles[type]}>My styled button</button>
}

这些示例应该能帮你完成 80% 的学习。如果你遇到困难,可以参考一些现有的开源示例。

Sancho UI是一组使用 typescript 和 emotion 构建的 react 组件。

Blueprint是另一套用 typescript 构建的 React 组件。

文章来源:https://dev.to/bmcmahen/a-beginners-guide-to-using-typescript-with-react-7m6
PREV
SOLID:软件设计原则。成为更好的开发人员
NEXT
如何在您的网站中使用振动 API