我们制定了应对React中错误的策略

如何使秋天变得柔和?




我没有找到有关React应用程序中错误处理的全面指南,因此我决定分享本文中获得的经验。 本文适用于初学者,并且可以作为系统化应用程序中错误处理的起点。

问题和目标设定


星期一早上,您冷静地喝咖啡,并吹嘘自己已修正了比上周更多的错误,然后经理运行并挥舞着他的手-“我们跌倒了,一切都很难过,我们正在亏钱”。 您运行并打开Mac,转到SPA的生产版本,单击几次以播放bug,看到白屏,只有全能者知道那里发生了什么,爬到控制台中,开始挖掘,在组件内部有一个名称为b的组件,错误无法读取未定义的属性getId。 经过N个小时的研究,您急匆匆地哭诉着要发布此修补程序。 这样的袭击以一定的频率发生并且已经成为常态,但是如果我说一切都可以不同怎么办? 如何减少调试错误的时间并构建过程,以使客户端在开发过程中几乎不会注意到不可避免的错误计算?

让我们按顺序检查我们遇到的问题:

  1. 即使该错误无关紧要或在模块内定位,在任何情况下,整个应用程序都无法运行
    在React版本16之前,开发人员没有单一的标准错误捕获机制,并且在某些情况下,数据损坏导致仅在后续步骤中呈现下降或奇怪的应用程序行为。 每个开发人员都会处理错误,因为他已经习惯了它,并且带有try / catch的命令式模型通常与React的声明性原则不太匹配。 在版本16中,出现了“错误边界”工具,该工具试图解决这些问题,我们将考虑如何应用它。
  2. 该错误仅在生产环境中复制,或者在没有其他数据的情况下无法复制。
    在理想的世界中,开发环境与生产环境相同,我们可以在本地重现任何错误,但我们生活在现实世界中。 战斗系统上没有调试工具。 很难发掘此类事件,而且生产效率不高,基本上,您必须处理混乱的代码和缺少有关错误的信息,而不是问题的本质。 我们不会考虑如何将开发环境的条件与生产条件相近似的问题,但是,我们将考虑允许您获取有关已发生事件的详细信息的工具。

所有这些降低了软件产品的开发速度和用户忠诚度,因此我为自己设定了三个最重要的目标:

  1. 出现错误时改善应用程序的用户体验;
  2. 减少从生产错误到发现错误的时间;
  3. 为开发人员加快在应用程序中查找和调​​试问题的过程。

需要解决什么任务?

  1. 使用错误边界处理严重错误
    为了改善应用程序的用户体验,我们必须拦截并处理关键的UI错误。 如果应用程序由独立的组件组成,则这种策略将允许用户使用系统的其余部分。 如果可能,我们还可以尝试采取措施在崩溃后还原应用程序。
  2. 保存扩展错误信息
    如果发生错误,请将调试信息发送到监视服务器,监视服务器将过滤,存储和显示有关事件的信息。 这将帮助我们在部署后快速检测并轻松调试错误。

严重错误处理

从版本16开始,React更改了标准错误处理行为。 现在,使用错误边界未捕获到的异常将导致卸载整个React树,并因此导致整个应用程序的不可操作性。 事实是,最好不要展示任何东西,而不是让用户有机会获得无法预测的结果,这一事实证明了这一决定。 您可以在官方的React文档中阅读更多内容



此外,许多人都被注释所迷惑,即错误边界不会捕获事件处理程序和异步代码中的错误,但是如果您考虑一下,则任何处理程序最终都可以更改状态,根据该状态将调用新的呈现周期,最终帐户可能会导致UI代码错误。 否则,这对于UI来说不是关键错误,可以在处理程序内部以特定方式进行处理。

从我们的角度来看,一个严重错误是UI代码内部发生的异常,如果不对其进行处理,则会卸载整个React树。 其余错误并不重要,可以根据应用程序逻辑(例如使用通知)进行处理。

在本文中,我们将重点放在处理关键错误上,尽管在最坏的情况下非关键错误也会导致接口无法操作。 它们的处理很难分为一个公共块,每种情况都需要根据应用程序逻辑做出决定。

通常,非严重错误可能非常严重(例如双关语),因此有关错误的信息的记录方式应与严重错误相同。

现在,我们正在为简单的应用程序设计错误边界,它由导航栏,标题和主工作区组成。 它非常简单,仅关注于错误处理,但是对于许多应用程序却具有典型的结构。



我们有一个包含3个链接的导航面板,每个链接都导致彼此独立的组件,因此我们想要实现一种行为,即使其中一个组件不起作用,我们也可以继续使用其余的组件。

结果,对于每个可通过导航菜单访问的组件,我们将具有ErrorBoundary,并且在标题组件,导航面板或ErrorBoundary内部发生错误的情况下,该通用的ErrorBoundary会通知整个应用程序崩溃,但我们并未解决处理并进一步丢弃。

考虑列出包裹在ErrorBoundary中的整个应用程序

const AppWithBoundary = () => (   <ErrorBoundary errorMessage="Application has crashed">     <App/>   </ErrorBoundary> ) 

 function App() {  return (    <Router>      <Layout>        <Sider width={200}>          <SideNavigation />        </Sider>        <Layout>          <Header>            <ActionPanel />          </Header>          <Content>            <Switch>              <Route path="/link1">                <Page1                  title="Link 1 content page"                  errorMessage="Page for link 1 crashed"                />              </Route>              <Route path="/link2">                <Page2                  title="Link 2 content page"                  errorMessage="Page for link 2 crashed"                />              </Route>              <Route path="/link3">                <Page3                  title="Link 3 content page"                  errorMessage="Page for link 3 crashed"                />              </Route>              <Route path="/">                <MainPage                  title="Main page"                  errorMessage="Only main page crashed"                />              </Route>            </Switch>          </Content>        </Layout>      </Layout>    </Router>  ); } 

ErrorBoundary中没有魔术,它只是在其中定义componentDidCatch方法的类组件,也就是说,如果在其中定义了此方法,则可以使任何组件成为ErrorBoundary。

 class ErrorBoundary extends React.Component {  state = {    hasError: false,  }  componentDidCatch(error) {    //            this.setState({ hasError: true });  }  render() {    if (this.state.hasError) {      return (        <Result          status="warning"          title={this.props.errorMessage}          extra={            <Button type="primary" key="console">              Some action to recover            </Button>          }  />      );    }    return this.props.children;  } }; 

这是Page组件的ErrorBoundary外观,它将呈现到Content块中:

 const PageBody = ({ title }) => (  <Content title={title}>    <Empty className="content-empty" />  </Content> ); const MainPage = ({ errorMessage, title }) => (  <ErrorBoundary errorMessage={errorMessage}>    <PageBody title={title} />  </ErrorBoundary> 

由于ErrorBoundary是常规的React组件,我们可以使用相同的ErrorBoundary组件将每个页面包装在其自己的处理程序中,只需将不同的参数传递给ErrorBoundary,因为这些是类的不同实例,因此它们的状态不会相互依赖。

重要说明:ErrorBoundary只能在树中位于其下方的组件中捕获错误。

在下面的清单中,该错误不会被本地ErrorBoundary拦截,但是将被树上方的处理程序抛出和拦截:

 const Page = ({ errorMessage }) => (  <ErrorBoundary errorMessage={errorMessage}>    {null.toString()}  </ErrorBoundary> ); 

这里的错误是由本地的ErrorBoundary捕获的:

 const ErrorProneComponent = () => null.toString(); const Page = ({ errorMessage }) => (  <ErrorBoundary errorMessage={errorMessage}>    <ErrorProneComponent />  </ErrorBoundary> ); 

将每个单独的组件包装在我们的ErrorBoundary中,我们实现了必要的行为,使用link3将故意错误的代码放入该组件中,然后看看会发生什么。 我们故意忘记传递步骤参数:

 const PageBody = ({ title, steps }) => (  <Content title={title}>    <Steps current={2} direction="vertical">      {steps.map(({ title, description }) => (<Step title={title} description={description} />))}    </Steps>  </Content> ); const Page = ({ errorMessage, title }) => (  <ErrorBoundary errorMessage={errorMessage}>    <PageBody title={title} />  </ErrorBoundary> ); 



该应用程序将通知我们发生了一个错误,但是它不会完全消失,我们可以浏览导航菜单并与其他部分一起工作。



这种简单的配置使我们可以轻松实现目标,但实际上,很少有人会非常注意错误处理,仅计划应用程序的定期执行。

保存错误信息

既然我们已经在应用程序中放置了足够的ErrorBoundary,则有必要保存有关错误的信息,以便尽快检测和更正错误。 最简单的方法是使用SaaS服务,例如Sentry或Rollbar。 它们具有非常相似的功能,因此您可以使用任何错误监视服务。

我将在Sentry上展示一个基本示例,因为在短短的一分钟内您将获得最少的功能。 同时,Sentry本身会捕获异常,甚至修改console.log以获取所有错误信息。 之后,将在应用程序中发生的所有错误发送并存储在服务器上。 Sentry具有过滤事件,混淆个人数据,链接到发布等的机制。 我们将仅考虑基本的集成方案。

要进行连接,您必须在其官方网站上注册并阅读快速入门指南,该指南将在注册后立即为您提供指导。

在我们的应用程序中,我们只添加了几行,一切就开始了。

 import * as Sentry from '@sentry/browser'; Sentry.init({dsn: “https://12345f@sentry.io/12345”}); 

再次,单击应用程序中的link / link3并显示错误屏幕,然后转到哨兵界面,这显然是发生了事件并且内部失败。



错误会根据类型,发生的频率和时间自动分组;可以应用各种过滤器。 我们有一个事件-进入该事件,然后在下一个屏幕上看到一堆有用的信息,例如堆栈跟踪



以及错误之前的最后一个用户操作(面包屑)。



即使配置如此简单,我们也可以累积并分析错误信息,并将其用于进一步的调试。 在此示例中,在开发模式下从客户端发送了一个错误,因此我们可以观察到有关组件和错误的完整信息。 为了从生产模式中获取类似的信息,您还必须使用Sentry配置发布数据的同步,Sentry将在其自身中存储源映射,从而使您可以保存足够的信息而不会增加包的大小。 我们不会在本文的框架中考虑这种配置,但是在实现之后,我将在另一篇文章中尝试讨论这种解决方案的陷阱。

结果:

使用ErrorBoundary进行错误处理可以使我们在应用程序部分崩溃的情况下得以解决,从而增加了系统的用户体验,并使用了专用的错误监视系统来减少检测和调试问题的时间。

仔细考虑用于处理和监视应用程序错误的策略,将来这将为您节省大量时间和精力。 一个经过深思熟虑的策略将首先改善处理事件的过程,然后才影响代码的结构。

PS您可以尝试使用各种ErrorBoundary配置选项,也可以在feature_sentry分支中将Sentry自己连接到应用程序,用在网站上注册期间获得的密钥替换密钥。

Git-hub演示应用程序
React的官方错误边界文档

Source: https://habr.com/ru/post/zh-CN476284/


All Articles