如何在TypeScript中编写React组件(详细指南)

39 阅读5分钟

这里是我们的组件,没有类型:

const operations = {
  '+': (left, right) => left + right,
  '-': (left, right) => left - right,
  '*': (left, right) => left * right,
  '/': (left, right) => left / right,
}

function Calculator({left, operator, right}) {
  const result = operations[operator](left, right)
  return (
    
      
        {left} {operator} {right} = {result}
      
    
  )
}

const examples = (
  <>
    
    
    
    
  
)

就在这里,你可能会注意到我们做事的方式有点不同。也许你更喜欢这样:

const Calculator = ({left, operator, right}) => (
  
    
      {left} {operator} {right} ={' '}
      {operations[operator](left, right)}
    
  
)

我不喜欢那里的隐式返回。这意味着你不能合理地声明变量或使用钩子。所以,即使是简单的组件,我也不会采用这种方法。

好吧,那么也许你会这样做:

const Calculator = ({left, operator, right}) => {
  const result = operations[operator](left, right)
  return (
    
      
        {left} {operator} {right} = {result}
      
    
  )
}

说实话,大多数时候这样做是可以的。我个人喜欢函数声明的吊装特性,而不是像这样的函数表达式(了解更多)。

好了,让我们在此添加一些类型。对于函数,你需要考虑输入的类型和输出的类型。让我们从输入开始:道具。首先,让我们用一个简单的类型来表示道具(我们以后会改进它):

type CalculatorProps = {
  left: number
  operator: string
  right: number
}

有了这个,让我们尝试一些选项,把这个类型应用到我们React组件中的props对象。

为React组件打字的一个常见方法是使用内置在@types/react 中的一个泛型(我的意思是,它是内置的,对吗?所以有什么可错的?)有趣的是,你不能以这种方式输入函数声明,所以我们必须使用函数表达式:

const Calculator: React.FC = ({left, right, operator}) => {
  // implementation clipped for brevity
}

这样做很好,但有三个主要问题:

  1. 我们的Calculator 函数现在接受了一个children 道具,尽管我们没有对它做任何事情🙃(所以,这样编译:What? )。
  2. 你不能使用泛型。这不是一个超级常见的问题,但绝对是一个弊端。
  3. 我们必须使用一个函数表达式,不能使用函数声明。

好吧,也许#3不是一个主要问题,但#1是相当重要的。如果你想深入研究的话,在这个优秀的GitHub问题上还有一些其他的小问题(也可以查看React TypeScript Cheatsheet)。简单地说,不要使用React.FC (或其更长的别名React.FunctionComponent )。

我喜欢React组件的一个原因是,它们并不那么特别。下面是React组件的定义。

一个React组件是一个返回React可以渲染的东西的函数。

现在,根据@types/react ,我们只限于nullJSX.Elements,但实际上React也可以渲染字符串、数字和布尔。在任何情况下,因为React组件只是一个返回React可以渲染的东西的函数,输入它可以像输入函数一样直接。你不需要因为它是React而做任何特别的事情。

因此,这里是我为这个组件输入道具的方式:

function Calculator({left, operator, right}: CalculatorProps) {
  // implementation clipped for brevity
}

这没有React.FC 的任何缺点,也没有比输入普通函数的参数更复杂。

好吧,那么返回值呢?好吧,我们可以把它打成React.ReactElement ,或者甚至打成更宽的JSX.Element 。但说实话,我支持我的朋友Nick McCurdy的观点,他说很容易犯错误,导致返回类型太宽。因此,即使在react语境之外,我也默认不指定返回类型(依靠推理),除非必要。这里就是这样的情况。

好了,接下来的内容与React组件的类型没有太大关系,但我想你还是会觉得很有趣,所以如果你不喜欢就跳过吧。让我们改进一下CalculatorProps 的类型。作为提醒,这是我们目前的情况:

// I took the liberty of typing each of these functions as well:
const operations = {
  '+': (left: number, right: number): number => left + right,
  '-': (left: number, right: number): number => left - right,
  '*': (left: number, right: number): number => left * right,
  '/': (left: number, right: number): number => left / right,
}

type CalculatorProps = {
  left: number
  operator: string
  right: number
}
function Calculator({left, operator, right}: CalculatorProps) {
  const result = operations[operator](left, right)
  return (
    
      
        {left} {operator} {right} = {result}
      
    
  )
}

我认为leftright 类型很好。我不满意的是operator 。使用string 是太宽了。有一些特定的操作是允许的。例如,如果我们尝试,会发生什么:

const element = 

这就是我们所说的运行时异常,我的朋友。也就是说......除非你打开了strict 模式,在这种情况下,你会在operations[operator] 上出现一个编译错误。在严格模式下,TypeScript会正确地知道,从operations 对象中访问任何字符串都不一定会返回一个可调用的函数。

有很多方法来解决这个问题。基本上,我们想把operator ,只限于支持的运算符。我们可以用一个简单的联合类型来做到这一点:

type CalculatorProps = {
  left: number
  operator: '+' | '-' | '*' | '/'
  right: number
}

但是如果我们决定添加指数运算符(**),那么我们不仅要更新operations 对象,还要更新operator 类型,这就很烦人了。也许我们有办法在operations 对象的基础上推导出operator 的类型?为什么,有的!?

type CalculatorProps = {
  left: number
  operator: keyof typeof operations
  right: number
}

typeof operations 是要让我们得到一个描述 对象的类型,这大致上等于。operations

type operations = {
  '+': (left: number, right: number) => number
  '-': (left: number, right: number) => number
  '*': (left: number, right: number) => number
  '/': (left: number, right: number) => number
}

keyof 部分将采取该类型的所有键,结果是'+' | '-' | '*' | '/' 🎉。

这是完成的版本(我也打了操作函数)。

const operations = {
  '+': (left: number, right: number): number => left + right,
  '-': (left: number, right: number): number => left - right,
  '*': (left: number, right: number): number => left * right,
  '/': (left: number, right: number): number => left / right,
}

type CalculatorProps = {
  left: number
  operator: keyof typeof operations
  right: number
}

function Calculator({left, operator, right}: CalculatorProps) {
  const result = operations[operator](left, right)
  return (
    
      
        {left} {operator} {right} = {result}
      
    
  )
}

const examples = (
  <>
    
    
    
    
  
)

我希望这能让你了解到给你的React组件打字的一个好方法。祝你好运,保重