反应`componentDidCatch`没有被调用但需要处理错误?

时间:2018-02-22 21:01:26

标签: javascript ios reactjs cordova

我尝试使用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>
    );
  }
}

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

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

此错误出现在XCode控制台中,上面没有错误。 console.log中的componentDidCatch永远不会运行。 <{1}}和catch中的componentDidMount块也永远不会到达。

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

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

1 个答案:

答案 0 :(得分:3)

事情是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>
    );
  }
}

如果您需要捕获这些类型的错误,请使用handlePlayerRef内的传统try / catch块。

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

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

您是否尝试构建一个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
   }
}

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

<ErrorBoundary>
  <VideoOverlay />
</ErrorBoundary>

详细阅读here