如何处理 React 中的错误

作为开发者如何优雅的处理错误是至关重要的,否则页面出现白屏影响用户体验甚至流失用户。

下面通过不同的方式来处理 React 中的错误。

try...catch

js 中捕获错误用的最多的方式就是 try...catch

try {
  // do something...
} catch (e) {
  console.error(e)
}

在 React 中的某些场景下也会用到 try...catch,比如网络请求:

const fetchData = async () => {
  try {
    return await fetch('http://xxx.com')
  } catch (error) {
    console.error(error)
  }
}

遗憾的是 try...catch 仅适用于命令式代码,不适用于组件中编写的 JSX 之类的声明式代码。所以这就是为什么不会把整个应用程序包裹在 try...catch 中。

React 错误边界

了解错误边界之前先考虑下为什么需要错误边界,假如有一个这样的组件:

const AppComponent = props => {
  return {props.userinfo.name}
}

export default AppComponent

function App() {
  return 
}

当访问不存的 props.userinfo 时,出现如下错误而导致整个页面空白,用户将无法操作或查看任何内容。但这种情况无法用 try...catch 捕获错误。

image.png

部分 js 错误不应该导致整个程序崩溃,为了解决这个问题,React 16 引入了一个新的概念 —— 错误边界。

错误边界是一种 class 组件,这种组件可以捕获发生在其子组件树任何位置的 js 错误,并打印这些错误,同时展示降级 UI,而并不会渲染那些发生错误的子组件树。错误边界可以捕获发生在整个子组件树的渲染期间、生命周期方法以及构造函数中的错误。该组件定义了一个(或两个)生命周期方法 static getDerivedStateFromError()componentDidCatch()时,就变成了错误边界。当抛出错误后使用static getDerivedStateFromError() 渲染备用 UI。 使用componentDidCatch() 打印错误信息或者将错误上报远程服务。

import React, { Component } from 'react'

class ErrorComp extends Component {
  constructor(props) {
    super(props)

    this.state = {
      hasError: false
    }
  }

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染能够显示降级后的 UI
    return {
      hasError: true,
      error
    }
  }

  componentDidCatch(error, errorInfo) {
    // 收集错误
    console.log(error)
    console.log(errorInfo)
  }

  render() {
    const { hasError, error } = this.state

    if (hasError) {
      return (
        

出错了~

{error.message && 错误信息: {error.message}}
) } return this.props.children } } const AppComponent = props => { return {props.userinfo.name} } function App() { return ( ) } export default App

错误边界并不是万能的。不会捕获以下错误:

  1. 事件处理器
  2. 异步(例如 setTimeout 或 requestAnimationFrame 回调)
  3. 服务端渲染
  4. 在错误边界本身(而不是其子项)中引发的错误

仍然需要 try...catch 处理。

事件处理程序中的错误捕获

错误边界无法捕获事件处理器内部的错误。而且也不需要错误边界来捕获事件处理器中的错误,因为事件处理器与 render、生命周期不同,触发的时机不是在渲染期间。因此如果事件处理中出现错误,仍然需要追踪上报那么就需要 try...catch:

class MyComponent extends Component {
  state = {
    error: null
  }

  handleClick = () => {
    try {
      // 执行操作,如有错误则会抛出
      throw Error('oops...')
    } catch (error) {
      this.setState({ error })
    }
  }

  render() {
    console.log(this.state)
    if (this.state.error) {
      return 

出错了 {this.state.error.message}

} return } }

setTimeout 调用中的错误捕获

改造 handleClick:

handleClick = () => {
  setTimeout(() => {
    try {
      // 执行操作,如有错误则会抛出
      throw Error('oops...')
    } catch (error) {
      this.setState({ error })
    }
  }, 1000)
}

react-error-boundary

社区提供了一种错误边界的三方组件。

pnpm add react-error-boundary
import React, { ErrorBoundary } from 'react-error-boundary'

const AppComponent = props => {
  return {props.userinfo.name}
}

const ErrorFallback = ({ error, resetErrorBoundary }) => (
  

出错了~

{error.message && 错误信息: {error.message}}
) function App() { return ( { console.error(error) console.log(componentStack) }} onReset={() => { console.log('reset') }} > ) }

该模块还有一个好处是提供了 withErrorBoundary方法,可以将函数组件包装成高阶组件(HOC)

import React, { withErrorBoundary } from 'react-error-boundary'

const AppComponent = props => {props.userinfo.name}
const ErrorFallback = ({ error, resetErrorBoundary }) => (
  

出错了~

{error.message && 错误信息: {error.message}}
) const App = withErrorBoundary(AppComponent, { FallbackComponent: ErrorFallback, onError: (error, { componentStack }) => { console.error(error) console.log(componentStack) } }) export default App

自己实现 React 错误边界

import React, { Component } from 'react'

const initialState = { error: null }

export default class ErrorBoundary extends Component {
  static getDerivedStateFromError(error) {
    return {
      error
    }
  }

  state = initialState

  resetErrorBoundary = (...args) => {
    this.props.onReset?.(...args)
    this.reset()
  }

  reset() {
    this.setState(initialState)
  }

  componentDidCatch(error, errorInfo) {
    this.props.onError?.(error, errorInfo)
  }

  render() {
    const { error } = this.state
    const { FallbackComponent } = this.props
    const props = {
      error,
      resetErrorBoundary: this.resetErrorBoundary
    }

    if (error !== null) {
      return 
    }

    return this.props.children
  }
}

function withErrorBoundary(ChildComp, errorBoundaryProps) {
  return props => (
    
      
    
  )
}

export { ErrorBoundary, withErrorBoundary }

总结

React 错误边界非常适合在声明式代码中捕获错误。对于其他情况,需要使用 try...catch(例如,异步调用 setTimeout,事件处理程序,服务器端渲染以及错误边界本身引发的错误)。

你可能感兴趣的:(如何处理 React 中的错误)