如何用TypeScript打出React组件

93 阅读6分钟

如何在React组件中使用TypeScript

在这篇文章中,我将讨论为什么以及如何使用TypeScript为React组件打字。

你会发现如何注解组件的道具,标记道具的可选性,以及指示返回类型。

目录

1.为什么要给React组件打字?

如果你正在编写中型和大型网络应用程序,TypeScript很有用。注释变量、对象和函数可以在你的应用程序的不同部分之间创建契约。

例如,假设我是一个在屏幕上显示格式化日期的组件的作者。

tsx

interface FormatDateProps {
  date: Date
}
 
function FormatDate({ date }: FormatDateProps): JSX.Element {
  return <div>{date.toLocaleString()}</div>;
}

根据FormatDateProps 接口,组件FormatDate 的值date 的道具只能是Date 的一个实例。这就是一个约束

为什么这个约束很重要?因为FormatDate 组件在日期实例上调用方法date.toLocaleString() ,而date prop必须是一个日期实例。否则,这个组件就不能工作。

那么,FormatDate 组件的用户就必须满足这个约束条件,并且只向date prop提供Date 实例。

tsx

<FormatDate
  date={new Date()}
/>

如果用户忘记了这个约束,比如说给date prop提供一个字符串"Sep 28 2021"

tsx

<FormatDate
  date="Sep 28 2021"
Type 'string' is not assignable to type 'Date'.
/>

那么TypeScript将显示一个类型错误。

这很好,因为错误会在开发过程中被发现,而不会隐藏在代码库中。

2.类型化道具

在我看来,React从TypeScript获得的最好的好处是道具类型化。

对React组件进行类型化通常是一个两步过程。

A) 定义接口,描述该组件使用对象类型接受哪些道具。Props接口的一个好的命名规则是ComponentName +Props =ComponentNameProps

B) 然后使用该接口来注释功能组件函数内的道具参数。

例如,让我们注释一个接受2个道具的组件Messagetext (一个字符串)和important (一个布尔值)。

tsx

interface MessageProps {
  text: string;
  important: boolean;
}
 
function Message({ text, important }: MessageProps) {
  return (
    <div>
      {important ? 'Important message: ' : 'Regular message: '}
      {text}
    </div>
  );
}

MessageProps 是描述该组件接受的道具的接口: 道具为 , 为 。text string important boolean

现在,当渲染组件时,你将不得不根据道具类型来设置道具值。

tsx

<Message
  text="The form has been submitted!"
  important={false}
/>

基本道具类型为不同种类的道具提出了类型。使用该列表作为灵感。

2.1 道具验证

现在,如果你碰巧为组件提供了错误的道具集,或者错误的值类型,那么TypeScript将在编译时警告你错误的道具值。

通常情况下,一个错误会在以下某个阶段被发现--类型检查、单元测试、集成测试、端到端测试、来自用户的错误报告--而你越早发现这个错误越好

如果Message 组件渲染的道具值是无效的。

tsx

<Message
  text="The form has been submitted!"
  important={0}
Type 'number' is not assignable to type 'boolean'.
/>

或没有道具:

tsx

<Message
Property 'text' is missing in type '{ important: true; }' but required in type 'MessageProps'.
  important={true}
/>

那么TypeScript会对此发出警告。

2.2儿童道具

children 是React组件中的一个特殊道具:当组件被渲染时,它保存着开端和结束标签之间的内容: 。<Component>children</Component>

大多数情况下,children 道具的内容是一个JSX元素,它可以使用一个特殊的类型JSX.Element (在React环境中全局可用的类型)进行输入。

让我们稍微改变一下Message 组件,以使用children prop。

tsx

interface MessageProps {
  children: JSX.Element | JSX.Element[];
  important: boolean;
}
 
function Message({ children, important }: MessageProps) {
  return (
    <div>
      {important ? 'Important message: ' : 'Regular message: '}
      {children}
    </div>
  );
}

看看接口中的children prop:它接受一个单一的元素JSX.Element 或一个元素数组JSX.Element[]

现在你可以用一个元素作为子元素来表示消息。

tsx

<Message important={false}>
  <span>The form has been submitted!</span>
</Message>

或多个子元素: tsx

<Message important={false}>
  <span>The form has been submitted!</span>
  <span>Your request will be processed.</span>
</Message>

挑战:你将如何更新MessageProps 接口,以便同时支持一个简单的string 值作为子代?请在下面的评论中写出你的解决方案!

2.3 可选道具

要使一个道具在道具界面中成为可选,可以用一个特殊的符号标志?

例如,让我们把important 这个道具标记为可选的。

tsx

interface MessageProps {
  children: JSX.Element | JSX.Element[];
  important?: boolean;
}
 
function Message({ children, important = false }: MessageProps) {
  return (
    <div>
      {important ? 'Important message: ' : 'Regular message: '}
      {children}
    </div>
  );
}

MessageProps 接口里面,important 道具被标记为? -important?: boolean - 使得这个道具是可选的。

Message 函数里面,我还为important 道具添加了一个false 的默认值:{ children, important = false } 。这将是默认值,如果important 道具没有被指明的话。

现在TypeScript允许你跳过important 这个道具。

tsx

<Message>
  <span>The form has been submitted!</span>
</Message>

当然,如果你愿意,你仍然可以使用important

tsx

<Message important={true}>
  <span>The form has been submitted!</span>
</Message>

3.返回类型

在前面的例子中,Message 函数并没有明确指出它的返回类型。这是因为TypeScript很聪明,可以推断出函数的返回类型 -JSX.Element

tsx

type MessageReturnType = ReturnType<typeof Message>;
            
type MessageReturnType = JSX.Element

在React功能组件的情况下,返回类型通常是JSX.Element

tsx

function Message({ 
    children, 
    important = false 
  }: MessageProps): JSX.Element {
  return (
    <div>
      {important ? 'Important message: ' : 'Regular message: '}
      {children}
    </div>
  );
}

有些情况下,组件在某些条件下可能什么都不返回。如果是这种情况,只需使用一个联合JSX.Element | null 作为返回类型。

tsx

interface ShowTextProps {
  show: boolean;
  text: string;
}
 
function ShowText({ show, text }: ShowTextProps): JSX.Element | null {
  if (show) {
    return <div>{text}</div>;
  }
  return null;
}

ShowText 如果 prop是 ,则返回一个元素,否则返回 。这就是为什么 函数的返回类型是一个union 。show true null ShowText JSX.Element | null

3.1 提示:强制执行返回类型

我的建议是强制每个函数明确指出返回类型。通过这样做,许多愚蠢的错误和错别字都可以被发现。

例如,如果你不小心在return 和返回的表达式之间设置了一个换行,那么明确指出的返回类型就会抓住这个问题。

tsx

function BrokenComponent(): JSX.Element {
  return
Type 'undefined' is not assignable to type 'Element'.
    <div>Hello!</div>;
}

(注意:当return 关键字和表达式之间有一个换行时,那么函数就会返回undefined ,而不是表达式。)

然而,如果没有指明返回类型,错误使用的return 就不会被TypeScript注意到(也不会被你注意到!)。

tsx

function BrokenComponent() {
  return 
    <div>Hello!</div>;
}

那就祝你调试顺利吧!

4.总结

React组件可以从TypeScript中大大受益。

类型化组件对于验证组件的道具特别有用。通常,这是由定义一个接口来完成的,每个道具都有它的类型。

然后,当注解的组件渲染时,TypeScript会验证是否提供了正确的道具值。

在数据验证的基础上,类型可以成为元信息的一个重要来源,提供注释的函数或变量如何工作的线索。