React `componentDidCatch` 未被调用但需要处理错误?

Sim*_*leJ 4 javascript ios cordova reactjs

我正在尝试使用 React 的componentDidCatch方法来捕获导致我的 Cordova iOS 应用程序崩溃的错误。它似乎在防止崩溃,但该方法从未被调用,所以我不知道是什么导致了问题。

我的组件:

import styled from 'styled-components';
import { View, observable } from 'shared/View';
import { Player } from 'video-react';
import SpinnerOverlay from 'shared/components/SpinnerOverlay';
import * as Icons from 'shared/components/Icons';
import fs from 'fileSystem';

export default class VideoOverlay extends View {
  @observable ready = false;

  handlePlayerStateChange = (state, prevState) => {
    const { onClose } = this.props;

    if(state.paused && !prevState.paused && onClose) {
      try {
        onClose();
      } catch(error) {
        console.log('--- onClose error', error);
      }
    }
  };

  handlePlayerRef = (player) => {
    player.subscribeToStateChange(this.handlePlayerStateChange);
    player.play();
  };

  async componentDidMount() {
    try {
      const { mediaFile } = this.props;
      await mediaFile.download(fs);
      this.ready = true;
    } catch(error) {
      console.log('--- SHIT', error);
    }
  }

  componentDidCatch(error) {
    // Needed because `Player` throws an error when unmounting.
    // This method doesn't get called (not sure why), but without this method,
    // the app crashes when a video is closed.
    console.log('Caught', error);
  }

  render() {
    const { ready } = this;
    const { mediaFile } = this.props;
    const src = ready && mediaFile.fileSrc;

    return (
      <React.Fragment>
        <SpinnerOverlay visible/>
        {ready &&
          <Player ref={this.handlePlayerRef}>
            <source src={src}/>
          </Player>
        }
      </React.Fragment>
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

该组件工作正常,除非卸载时,它始终抛出此错误:

2018-02-22 12:52:19.706530-0800 App[1094:598984] ERROR: The above error occurred in the <Player> component:
    in Player (created by Component)
    in Component (created by Component)
    in div (created by Screen)
    in Screen (created by Component)
    in Component (created by inject-Component-with-store)
    in inject-Component-with-store (created by Route)
    in Route (created by Component)
    in Switch (created by Component)
    in Component (created by inject-Component-with-store)
    in inject-Component-with-store (created by Route)
    in Route (created by Component)
    in Component (created by Route)
    in Route (created by withRouter(Component))
    in withRouter(Component) (created by inject-withRouter(Component)-with-api)
    in inject-withRouter(Component)-with-api (created by Component)
    in Switch (created by Component)
    in div (created by App__Root)
    in App__Root (created by Component)
    in Component (created by Route)
    in Route (created by withRouter(Component))
    in withRouter(Component)
    in Router (created by HashRouter)
    in HashRouter
    in Provider
Run Code Online (Sandbox Code Playgroud)

此错误出现在 XCode 控制台中,上面没有错误。在console.logcomponentDidCatch从未运行。和中的catch块也永远不会到达。componentDidMounthandlePlayerStateChange

最奇怪的是,如果我删除componentDidCatch,应用程序会因上述错误而崩溃,因此componentDidCatch似乎正在做某事;它只是不让我真正处理错误。

此外,我无法在浏览器中重现此问题,因为此组件仅适用于 iOS(此组件利用了 iOS 对自动播放视频的自动全屏显示)。

小智 5

问题是componentDidCatch不会捕获事件处理程序错误,就像您的handlePlayerRef方法中的处理程序会发生的错误一样。这是因为这些类型的错误不会在组件呈现期间发生,而这正是didCatch.

例如,这不会被正确捕获和记录didCatch

class MyComponent extends Component {
  componentDidCatch() {
    console.log('whats wrong?')
  }

  onButtonClick() {
    //errors emerging here
  }

  render() {
    return (
      <button onClick={this.onButtonClick}>Click</button>
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

如果您需要捕获这些类型的错误,请在handlePlayerRef.

handlerPlayerRef() {
  try {
    player.subscribeToStateChange(this.handlePlayerStateChange);
    player.play();
  } catch () {
    console.error('Something wrong happened');
  }
}
Run Code Online (Sandbox Code Playgroud)

此外,通常最好将组件渲染错误与组件功能分离,因为它往往会产生难以调试的神秘错误。

您是否尝试过构建 ErrorBoundary 组件来包装 VideoOverlay 类?这样做并将其呈现为不存在错误:

class ErrorBoundary extends React.Component {
  componentDidCatch(err) {
    this.setState({
      hasError: true
    });
  }

  render() {
    return this.state.hasError 
      ?  <h2>Oh noes! Something went wrong.</h2>
      :  this.props.children
   }
}
Run Code Online (Sandbox Code Playgroud)

现在,您可以将 VideoOverlay 作为 ErrorBoundary 的子项提供:

<ErrorBoundary>
  <VideoOverlay />
</ErrorBoundary>
Run Code Online (Sandbox Code Playgroud)

这里阅读更多详细信息