在 SonarQube 中升级到 React 18 的经验教训

声纳Qube 接口写在 反应 我们最近经历了从版本 17 升级到 18 的过程。为了给您提供更多信息,该应用程序也是用 打字稿 和用途 反应测试库 (RTL)进行测试。

我们想分享我们在进行升级时面临的三个最大问题以及我们吸取的教训。 简而言之,他们是:

  1. 一些 TypeScript 类型发生了变化
  2. React 测试库也必须更新
  3. React 18 带来了重大变化

让我们来了解一下这些意味着什么以及我们如何处理它们。

注:本文由 SonarQube 前端团队 David Cho-Lerat、Ambroise Christea 和 Philippe Perrin 共同撰写。

TypeScript 类型更改

React 18 升级指南 指出两者 @types/react@types/react-dom 必须在升级时进行更新,“最显着的变化是 children 现在在定义 props 时需要明确列出 props。”

这次更新的好消息是 塞巴斯蒂安·西尔伯曼来自 React 核心团队,维护着 代码模组集合 这有助于从 React 17 升级时自动更新类型。

您可以使用 npx 运行 codemod,如下所示:

npx types-react-codemod preset-18 ./src

它将显示您可以应用的许多转换,并将默认为所需的转换。

例如,转换为列出 children prop 明确将采用如下所示的组件:

MyComponent: React.ComponentType

并将其替换为:

MyComponent: React.ComponentType>

但请注意,我们发现 codemod 最终可能会嵌套 PropsWithChildren type 并且你的类型最终可能看起来像这样:

MyComponent: React.ComponentType>>

虽然这没有什么害处,但当您遇到这些类型时,您将需要更正它们。

新类型在某些方面也更加挑剔。 例如,以前我们能够覆盖 children 在这样的界面中:

interface ComponentProps {
  children: React.ReactNode;
}

interface Props extends ComponentProps {
  children: () => React.ReactNode;
}

对于 React 18 类型,这不再有效,您现在必须省略声明 children 第一的。

interface Props extends Omit  {
  children: () => React.ReactNode;
}

新类型也不允许隐式 any a 的参数类型 useCallback 功能。 您需要显式声明类型,例如:

import { useCallback, MouseEvent } from 'react';

export function SubmitButton(props: ButtonProps) {
  const handleClick = useCallback((event: MouseEvent) => {
    event.preventDefault();
    // Do something else.
  }, [...]);
  return ;
}

当你升级时 @types/react 到版本 18,预计会看到一些这样的问题。

React 测试库更新

我们发现,在更新 React 和 RTL 后,许多以前可以通过的测试现在都失败了。 失败分为两类:时机失败和调用失败 act()

假定时器

RTL 使用一个 setTimeout 对于定义的延迟 模拟用户事件时,但这并不能很好地发挥作用 Jest 的假定时器。 这导致测试挂起并因超时而失败。

在14.1.0版本中,RTL添加了一个 advanceTimers 选项 到设置步骤 用户事件 这样您就可以提供自己的计时器。 我们能够通过通过 jest.advanceTimersByTime 方法。

const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime })

付诸行动

害怕的 act(...) 警告 困扰我们的代码库一段时间了,在某些情况下已经通过添加额外的调用来修补 act 围绕一些 RTL 事件和助手。

RTL 助手使用 act 在内部,所以在添加额外的调用时 act 最初是抑制警告的有效解决方法,但现在导致测试失败。 删除多余的调用 act 测试再次通过。 如果您仍然收到警告,Kent C. Dodds 有一篇关于 导致 act(…) 警告的原因以及如何修复它 在劳教的背景下。

React 18 项重大变更

React 18 最大的变化就在应用程序的根部。 ReactDOM.render 不再受支持,应替换为 createRoot。 虽然从表面上看,这似乎是一个简单的更改,为 React 提供了更好的方式来管理应用程序的根目录,但它实际上改变了 React 渲染应用程序的方式。 启用了两个新功能:自动批处理和新的并发渲染器。

如果有其他工作需要以更高的优先级完成,并发渲染允许 React 中断组件的渲染。 您可以通过使用以下方法将状态更新定义为转换来选择加入此行为 useTransition。 如果您不选择加入,您的组件将像以前一样按顺序呈现,因此在您升级应用程序时这应该没有什么区别。

但是,自动批处理会立即启用。 自动批处理是 React 18 中的一项性能改进,通过将状态更改收集到一个更新中来减少渲染数量。 但它可能会导致一些意外的行为。

当几个测试开始失败时,我们发现代码的某些部分与新的批处理发生了冲突。 测试期望渲染部分组件,但发现它们是空的。

我们意识到这个批处理包括同一范围内的任何执行子上下文! 这意味着如果您有 setState,然后是一个 Promise,它也执行 setState 当它解析/拒绝时,如果两个状态更改彼此发生的时间很接近,则它们将在范围结束时进行批处理(例如在测试中,模拟查询几乎是瞬时的)。

在这个基于类的组件中的两个方法的简化示例中,我们设置了一个状态,然后,在异步函数体内,我们在条件中依赖于该新状态。

class MyComponent extends React.Component {
  // ...

  fetchProjects = async () => {
    const { shouldFetch } = this.state;

    if (shouldFetch) {
      this.setState({ loading: true });
      const projects = await this.fetchProjects();
      this.setState({ loading: false, projects: projects });
    }
  }

  handleFetchProjectsClick = async () => {
    this.setState({ shouldFetch: true });
    await this.fetchProjects();
  }

  // ...
}

在 React 17 时 handleFetchProjectsClick 被称为它将设置 shouldFetch 陈述到 true,然后调用 fetchProjects。 之内 fetchProjects 的测试 shouldFetch 将会 true 并获取数据。 这是因为状态更新任务发生在 fetchProjects 承诺得到处理。

在 React 18 中 createRoot 未获取项目,因为状态更新被推迟到结束 handleFetchProjectsClick,所以当 fetchProjects 运行 shouldFetch 仍然是虚假的。

如果需要确保代码在设置状态后运行,可以使用 setState 的回调形式或新的 ReactDOM.flushSync() 方法。

class MyComponent extends React.Component {
  // ...

  handleFetchProjectsClick = async () => {
    this.setState({ shouldFetch: true }, () => {
      await this.fetchProjects();
    });
  }

  // ...
}

测试中的异步渲染

上面修复了页面上的组件渲染,但是我们发现测试仍然失败。 逐步调试这些失败表明内容在初始渲染中不存在,但是当我们重新渲染组件时它就出现了。 因为我们现在使用的是 setState 回调方法来获取数据,初始渲染不包含内容。

我们的测试使用 RTL 的同步方法来查找页面上的内容,如下所示:

expect(screen.getByText('Content')).toBeInTheDocument();

更换 RTL 同步 getBy 异步查询 await findBy 询问 解决了这个问题。 例如:

expect(await screen.findByText('Content')).toBeInTheDocument();

用一个 findBy 询问 用途 waitFor 在引擎盖下,当 DOM 没有立即更新时,给 DOM 有时间进行更新。

升级成功

SonarQube UI 现在在 React 18 上成功运行。虽然我们遇到的一些问题与需要升级的测试套件有关,但其他问题是因为我们有一个跨组件单元测试和端到端的全面测试套件-结束测试,避免在部署时出现生产故障。 编写可测试的代码是编写适应性代码的一部分,这是干净代码的属性之一。 这些测试强调了需要更新的内容,并让我们相信,当它们通过时,应用程序就准备好了。

如果您正在运行 React 17 并计划升级,希望这些经验可以帮助您解决一些陷阱。

Leave a Reply

Your email address will not be published. Required fields are marked *

近期新闻​

编辑精选​