diff --git a/components/alert/ErrorBoundary.tsx b/components/alert/ErrorBoundary.tsx new file mode 100644 index 0000000000..668ef94156 --- /dev/null +++ b/components/alert/ErrorBoundary.tsx @@ -0,0 +1,41 @@ +import * as React from 'react'; +import Alert from '.'; + +interface ErrorBoundaryProps { + message?: React.ReactNode; + description?: React.ReactNode; +} + +export default class ErrorBoundary extends React.Component< + ErrorBoundaryProps, + { + error?: Error | null; + info: { + componentStack?: string; + }; + } +> { + state = { + error: undefined, + info: { + componentStack: '', + }, + }; + + componentDidCatch(error: Error | null, info: object) { + this.setState({ error, info }); + } + + render() { + const { message, description, children } = this.props; + const { error, info } = this.state; + const componentStack = info && info.componentStack ? info.componentStack : null; + const errorMessage = typeof message === 'undefined' ? (error || '').toString() : message; + const errorDescription = typeof description === 'undefined' ? componentStack : description; + if (error) { + // You can render any custom fallback UI + return ; + } + return children; + } +} diff --git a/components/alert/__tests__/__snapshots__/demo.test.js.snap b/components/alert/__tests__/__snapshots__/demo.test.js.snap index a7a82672eb..a61fee70e9 100644 --- a/components/alert/__tests__/__snapshots__/demo.test.js.snap +++ b/components/alert/__tests__/__snapshots__/demo.test.js.snap @@ -1,5 +1,16 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`renders ./components/alert/demo/error-boundary.md correctly 1`] = ` + +`; + exports[`renders ./components/alert/demo/banner.md correctly 1`] = `
+ + ReferenceError: NotExisted is not defined + + + + in ThrowError + in ErrorBoundary (created by WrapperComponent) + in WrapperComponent + +
+`; diff --git a/components/alert/__tests__/index.test.js b/components/alert/__tests__/index.test.js index 9246fe0ef7..8b88773869 100644 --- a/components/alert/__tests__/index.test.js +++ b/components/alert/__tests__/index.test.js @@ -2,6 +2,8 @@ import React from 'react'; import { mount } from 'enzyme'; import Alert from '..'; +const { ErrorBoundary } = Alert; + describe('Alert', () => { beforeAll(() => { jest.useFakeTimers(); @@ -49,4 +51,16 @@ describe('Alert', () => { expect(input.getAttribute('role')).toBe('status'); }); }); + + const testIt = process.env.REACT === '15' ? it.skip : it; + testIt('ErrorBoundary', () => { + const ThrowError = () => ; // eslint-disable-line + const wrapper = mount( + + + , + ); + // eslint-disable-next-line jest/no-standalone-expect + expect(wrapper.render()).toMatchSnapshot(); + }); }); diff --git a/components/alert/demo/error-boundary.md b/components/alert/demo/error-boundary.md new file mode 100644 index 0000000000..5da6035733 --- /dev/null +++ b/components/alert/demo/error-boundary.md @@ -0,0 +1,51 @@ +--- +order: 8 +title: + zh-CN: ErrorBoundary + en-US: React 错误处理 +--- + +## zh-CN + +友好的 [React 错误处理](https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html) 包裹组件。 + +## en-US + +ErrorBoundary Component for making error handling easier in [React](https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html). + +```jsx +import { Button, Alert } from 'antd'; + +const { ErrorBoundary } = Alert; + +class ThrowError extends React.Component { + state = { + error: null, + }; + + onClick = () => { + this.setState({ + error: new Error('An Uncaught Error'), + }); + }; + + render() { + const { error } = this.state; + if (error) { + throw error; + } + return ( + + ); + } +} + +ReactDOM.render( + + + , + mountNode, +); +``` diff --git a/components/alert/index.en-US.md b/components/alert/index.en-US.md index 7e63f0d0aa..57db85eba4 100644 --- a/components/alert/index.en-US.md +++ b/components/alert/index.en-US.md @@ -25,3 +25,10 @@ Alert component for feedback. | showIcon | Whether to show icon | boolean | false, in `banner` mode default is true | | type | Type of Alert styles, options: `success`, `info`, `warning`, `error` | string | `info`, in `banner` mode default is `warning` | | onClose | Callback when Alert is closed | (e: MouseEvent) => void | - | + +### Alert.ErrorBoundary + +| Property | Description | Type | Default | Version | +| ----------- | -------------------------------- | --------- | ------------------- | ------- | +| message | custom error message to show | ReactNode | `{{ error }}` | | +| description | custom error description to show | ReactNode | `{{ error stack }}` | | diff --git a/components/alert/index.tsx b/components/alert/index.tsx index c57e2b6f3e..a3bf3eb9a0 100755 --- a/components/alert/index.tsx +++ b/components/alert/index.tsx @@ -16,6 +16,7 @@ import classNames from 'classnames'; import { ConfigConsumer, ConfigConsumerProps } from '../config-provider'; import getDataOrAriaProps from '../_util/getDataOrAriaProps'; +import ErrorBoundary from './ErrorBoundary'; function noop() {} @@ -65,6 +66,8 @@ const iconMapOutlined = { }; export default class Alert extends React.Component { + static ErrorBoundary = ErrorBoundary; + state = { closing: false, closed: false, diff --git a/components/alert/index.zh-CN.md b/components/alert/index.zh-CN.md index 404d8aaf67..fc3e0acf06 100644 --- a/components/alert/index.zh-CN.md +++ b/components/alert/index.zh-CN.md @@ -26,3 +26,10 @@ title: Alert | showIcon | 是否显示辅助图标 | boolean | false,`banner` 模式下默认值为 true | | type | 指定警告提示的样式,有四种选择 `success`、`info`、`warning`、`error` | string | `info`,`banner` 模式下默认值为 `warning` | | onClose | 关闭时触发的回调函数 | (e: MouseEvent) => void | 无 | + +### Alert.ErrorBoundary + +| 参数 | 说明 | 类型 | 默认值 | 版本 | +| --- | --- | --- | --- | --- | +| message | 自定义错误标题,如果未指定会展示原生报错信息 | ReactNode | `{{ error }}` | | +| description | 自定义错误内容,如果未指定会展示报错堆栈 | ReactNode | `{{ error stack }}` | | diff --git a/components/alert/style/index.less b/components/alert/style/index.less index 3e04bd8e4b..0d9e392815 100644 --- a/components/alert/style/index.less +++ b/components/alert/style/index.less @@ -63,9 +63,14 @@ &-error { background-color: @alert-error-bg-color; border: @border-width-base @border-style-base @alert-error-border-color; + .@{alert-prefix-cls}-icon { color: @alert-error-icon-color; } + + .@{alert-prefix-cls}-description { + white-space: pre; + } } &-close-icon { diff --git a/site/theme/template/Content/Demo/index.jsx b/site/theme/template/Content/Demo/index.jsx index 7f1bbe10ca..556c732e4f 100644 --- a/site/theme/template/Content/Demo/index.jsx +++ b/site/theme/template/Content/Demo/index.jsx @@ -5,14 +5,15 @@ import { FormattedMessage, injectIntl } from 'react-intl'; import CopyToClipboard from 'react-copy-to-clipboard'; import classNames from 'classnames'; import LZString from 'lz-string'; -import { Tooltip } from 'antd'; +import { Tooltip, Alert } from 'antd'; import { SnippetsOutlined, CheckOutlined, ThunderboltOutlined } from '@ant-design/icons'; import stackblitzSdk from '@stackblitz/sdk'; import CodePreview from './CodePreview'; import EditButton from '../EditButton'; -import ErrorBoundary from '../ErrorBoundary'; import BrowserFrame from '../../BrowserFrame'; +const { ErrorBoundary } = Alert; + function compress(string) { return LZString.compressToBase64(string) .replace(/\+/g, '-') // Convert '+' to '-' diff --git a/site/theme/template/Content/ErrorBoundary.js b/site/theme/template/Content/ErrorBoundary.js deleted file mode 100644 index 64ffbb2dc2..0000000000 --- a/site/theme/template/Content/ErrorBoundary.js +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import { Alert } from 'antd'; - -export default class ErrorBoundary extends React.Component { - state = { - error: null, - }; - - componentDidCatch(error, info) { - this.setState({ error, info }); - } - - render() { - const { children } = this.props; - const { error, info } = this.state; - if (error) { - // You can render any custom fallback UI - return ; - } - return children; - } -}