错误边界与使用技巧

getDerivedStateFromError

无法捕获错误的情况:

class Test extends React.Component {
  render() {
    return (
      <div>{ data.title }</div>
    )
  }
}
class Sub extends React.Component {
  constructor(props) {
    super(props);

    setTimeout(() => {
      throw new Error('This is a setTimeout error')
    }, 1000);
  }
  handleClick() {
    // 这种错误,建议用 try-catch 解决捕获
    throw new Error('This is a btn click error');
  }
  render() {
    return (
      <p onClick={ this.handleClick.bind(this) }>This is a content.</p>
    )
  }
}
class App extends React.Component {
  render() {
    return (
      <div>
        {/* 无法捕获到 Sub 中的捕获不到的情况的错误 */}
        <ErrorBoundary>
          <Sub />
        </ErrorBoundary>
        {/* 把可能出错的组件用错误边界组件包裹 */}
        <ErrorBoundary2>
          <ErrorBoundary>
            <Test />
          </ErrorBoundary>
        </ErrorBoundary2>
      </div>
    )
  }
}

class ErrorBoundary extends React.Component {
  state = {
    hasError: false
  }
  static getDerivedStateFromError(error) {
    // console.log(1); // 1先打印 getDerivedStateFromError渲染时调用
    return { hasError: true }
  }
  componentDidCatch(error, info) {
    // console.log(2); // 2后打印 componentDidCatch抛错后调用
    console.log(error, info);
  }
  render() {
    // 如果为真,显示备用UI
    if (this.state.hasError) {
      return (
        // <h1>This is a error UI.</h1>
        // 错误边界组件内部有错误,无法捕获:
        <h1>{ data.title }</h1>
      )
    }
    // 如果没错,显示 ErrorBoundary 包裹的本来的组件
    return this.props.children;
  }
}
class ErrorBoundary2 extends React.Component {
  constructor(props) {
    super(props);

    window.onerror = function(err) {
      console.log(err); // 能冒泡到 window.onerror
    }
  }
  state = {
    hasError: false
  }
  static getDerivedStateFromError(error) {
    return { hasError: true }
  }
  componentDidCatch(error, info) {
    console.log(error, info);
  }
  render() {
    if (this.state.hasError) {
      return (
        <h1>This is a error2 UI.</h1>
      )
    }
    return this.props.children;
  }
}
ReactDOM.render(<App />, document.getElementById('app'));

错误边界与 Suspense 和命名导出

import Loading from './17Loading.jsx';
import ErrorBoundary from './17ErrorBoundary.jsx';
const TestComponent = React.lazy(() => import('./17Test.jsx'));
const Test2Component = React.lazy(() => import('./17index.jsx'));
class App extends React.Component {
  render() {
    return (
      <div>
        <div>123</div>
          <React.Suspense fallback={ <Loading /> }>
            <ErrorBoundary>
              <TestComponent />
            </ErrorBoundary>
            <ErrorBoundary>
              <Test2Component />
            </ErrorBoundary>
          </React.Suspense>
      </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('app'));
class ErrorBoundary extends React.Component {
  state = {
    hasError: false
  }
  static getDerivedStateFromError(err) {
    return { hasError: true }
  }
  componentDidCatch(err, info) {
    console.log(err, info);
  }
  render() {
    if (this.state.hasError) {
      return <h1>This is a Error UI</h1>
    }
    return this.props.children;
  }
}
export default ErrorBoundary;
class Loading extends React.Component {
  render() {
    return (
      <div>Loading...</div>
    )
  }
}
export default Loading;
class Test extends React.Component {
  render() {
    return (
      // <div>This is a Test Component.</div>
      <div>{ data.title }</div>
    )
  }
}
export default Test;
class Test2 extends React.Component {
  render() {
    return (
      <div>This is a Test2 Component.</div>
    )
  }
}
export {
  Test2
}; // 不默认导出,会导致报错:Cannot convert object to primitive value
// 因为 lazy 只支持 export default 默认导出
// 如果想要命名导出,就得做中间层,见 17index.jsx

// 17index.jsx:
// export {
//   Test2 as default
// } from './17Test2.jsx';