Skip to content

Commit

Permalink
feat(ConfigProvider): add ErrorBoundary
Browse files Browse the repository at this point in the history
  • Loading branch information
youluna committed Mar 11, 2019
1 parent e08bbc6 commit ada2b5b
Show file tree
Hide file tree
Showing 8 changed files with 280 additions and 15 deletions.
80 changes: 80 additions & 0 deletions docs/config-provider/demo/error-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# ErrorBoundary 捕获错误

- order: 5

使用 `<ErrorBoundary>` 可以避免由于局部区域的错误,所引起的页面白屏。

:::lang=en-us
# Basic

- order: 5

`<ErrorBoundary>` can be used to avoid blank screen caused by local errors
:::

---

````jsx
import { ConfigProvider, Button } from '@alifd/next';

const { ErrorBoundary, config } = ConfigProvider;

class Demo extends React.Component {
render() {
return (
<span>{this.props.locale.ok}</span>
);
}
}

const NewDemo = config(Demo);

const fallbackUI = (props) => {
const { error, errorInfo } = props;
return <span style={{color: 'red'}}>Error: {error.toString()}</span>;
};

export default class App extends React.Component {
state = {
locale: {
ok: 'ok'
}
};

onClick = () => {
this.setState({
locale: undefined
});
};

render() {
return <div>
Pass undefined to locale which will cause an error: <Button type="primary" onClick={this.onClick}>trigger error</Button>
<br/>
<br/>
Default fallback UI:
<hr />
<ConfigProvider errorBoundary>
<NewDemo locale={this.state.locale}/>
</ConfigProvider>
<br/>
<br/>
Customize fallback UI of configed Component(Basic Components / Biz Components):
<hr />
<ConfigProvider errorBoundary={{
fallbackUI: props => {
const { error, errorInfo } = props;
return <span style={{color: 'red'}}>Error: {error.toString()}</span>;
},
afterCatch: () => {
console.log('catching');
}
}}>
<NewDemo locale={this.state.locale}/>
</ConfigProvider>
</div>
}
}

ReactDOM.render(<App />, mountNode);
````
1 change: 1 addition & 0 deletions docs/config-provider/index.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ export default config(Component);

| Param | Description | Type | Default Value |
| -------- | ----------------------------------- | ------------ | --- |
| errorBoundary | turn errorBoundary on or not<br>If you pass object, properties:<br><br>fallbackUI `Function(error?: {}, errorInfo?: {}) => Element` <br>afterCatch `Function(error?: {}, errorInfo?: {})` after being catched, e.g. send data to server for data statistics | Boolean/Object | false |
| pure | whether enable the Pure Render mode, it will improve performance, but it will also have side effects | Boolean | - |
| warning | whether to display the warning prompt for component properties being deprecated in development mode | Boolean | true |
| children | component tree | ReactElement | - |
Expand Down
13 changes: 7 additions & 6 deletions docs/config-provider/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,12 +214,13 @@ export default config(Component);

### ConfigProvider

| 参数 | 说明 | 类型 | 默认值 |
| -------- | ----------------------------------- | ------------ | ---- |
| pure | 是否开启 Pure Render 模式,会提高性能,但是也会带来副作用 | Boolean | - |
| warning | 是否在开发模式下显示组件属性被废弃的 warning 提示 | Boolean | true |
| rtl | 是否开启 rtl 模式 | Boolean | - |
| children | 组件树 | ReactElement | - |
| 参数 | 说明 | 类型 | 默认值 |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- | ----- |
| errorBoundary | 是否开启错误捕捉 errorBoundary<br>如需自定义参数,请传入对象 对象接受参数列表如下:<br><br>fallbackUI `Function(error?: {}, errorInfo?: {}) => Element` 捕获错误后的展示<br>afterCatch `Function(error?: {}, errorInfo?: {})` 捕获错误后的行为, 比如埋点上传 | Boolean/Object | false |
| pure | 是否开启 Pure Render 模式,会提高性能,但是也会带来副作用 | Boolean | - |
| warning | 是否在开发模式下显示组件属性被废弃的 warning 提示 | Boolean | true |
| rtl | 是否开启 rtl 模式 | Boolean | - |
| children | 组件树 | ReactElement | - |

<!-- api-extra-start -->

Expand Down
33 changes: 30 additions & 3 deletions src/config-provider/config.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import hoistNonReactStatic from 'hoist-non-react-statics';
import { obj, log } from '../util';
import getContextProps from './get-context-props';
import ErrorBoundary from './error-boundary';

const { shallowEqual } = obj;

Expand Down Expand Up @@ -89,6 +90,10 @@ export function config(Component, options = {}) {
locale: PropTypes.object,
pure: PropTypes.bool,
rtl: PropTypes.bool,
errorBoundary: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.object,
]),
};
static contextTypes = {
...(Component.contextTypes || {}),
Expand All @@ -97,6 +102,10 @@ export function config(Component, options = {}) {
nextPure: PropTypes.bool,
nextRtl: PropTypes.bool,
nextWarning: PropTypes.bool,
nextErrorBoundary: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.object,
]),
};

constructor(props, context) {
Expand Down Expand Up @@ -132,18 +141,26 @@ export function config(Component, options = {}) {
}

render() {
const { prefix, locale, pure, rtl, ...others } = this.props;
const {
prefix,
locale,
pure,
rtl,
errorBoundary,
...others
} = this.props;
const {
nextPrefix,
nextLocale = {},
nextPure,
nextRtl,
nextErrorBoundary = {},
} = this.context;

const displayName =
options.componentName || getDisplayName(Component);
const contextProps = getContextProps(
{ prefix, locale, pure, rtl },
{ prefix, locale, pure, rtl, errorBoundary },
{
nextPrefix,
nextLocale: { ...currentGlobalLocale, ...nextLocale },
Expand All @@ -154,10 +171,12 @@ export function config(Component, options = {}) {
: currentGlobalRtl === true
? true
: undefined,
nextErrorBoundary,
},
displayName
);

// errorBoundary is only for <ErrorBoundary>
const newContextProps = ['prefix', 'locale', 'pure', 'rtl'].reduce(
(ret, name) => {
if (typeof contextProps[name] !== 'undefined') {
Expand All @@ -172,13 +191,21 @@ export function config(Component, options = {}) {
? options.transform(others, this._deprecated)
: others;

return (
const content = (
<Component
{...newOthers}
{...newContextProps}
ref={this._getInstance}
/>
);

const { open, ...othersBoundary } = contextProps.errorBoundary;

return open ? (
<ErrorBoundary {...othersBoundary}>{content}</ErrorBoundary>
) : (
content
);
}
}

Expand Down
63 changes: 63 additions & 0 deletions src/config-provider/error-boundary.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react';
import PropTypes from 'prop-types';

DefaultUI.propTypes = {
error: PropTypes.object,
errorInfo: PropTypes.object,
};

function DefaultUI() {
return '';
}

export default class ErrorBoundary extends React.Component {
static propTypes = {
children: PropTypes.element,
/**
* 捕获错误后的自定义处理, 比如埋点上传
* @param {Object} error 错误
* @param {Object} errorInfo 错误详细信息
*/
afterCatch: PropTypes.func,
/**
* 捕获错误后的展现 自定义组件
* @param {Object} error 错误
* @param {Object} errorInfo 错误详细信息
* @returns {Element} 捕获错误后的处理
*/
fallbackUI: PropTypes.func,
};

constructor(props) {
super(props);
this.state = { error: null, errorInfo: null };
}

componentDidCatch(error, errorInfo) {
this.setState({
error: error,
errorInfo: errorInfo,
});

const { afterCatch } = this.props;

if ('afterCatch' in this.props && typeof afterCatch === 'function') {
this.props.afterCatch(error, errorInfo);
}
}

render() {
const { fallbackUI: FallbackUI = DefaultUI } = this.props;

if (this.state.errorInfo) {
return (
<FallbackUI
error={this.state.error}
errorInfo={this.state.errorInfo}
/>
);
}
// Normally, just render children
return this.props.children;
}
}
28 changes: 26 additions & 2 deletions src/config-provider/get-context-props.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
export default function getContextProps(props, context, displayName) {
const { prefix, locale, pure, rtl } = props;
const { nextPrefix, nextLocale, nextPure, nextWarning, nextRtl } = context;
const { prefix, locale, pure, rtl, errorBoundary } = props;
const {
nextPrefix,
nextLocale,
nextPure,
nextWarning,
nextRtl,
nextErrorBoundary,
} = context;

const newPrefix = prefix || nextPrefix;

Expand All @@ -21,11 +28,28 @@ export default function getContextProps(props, context, displayName) {
const newPure = typeof pure === 'boolean' ? pure : nextPure;
const newRtl = typeof rtl === 'boolean' ? rtl : nextRtl;

// ProtoType of [nextE|e]rrorBoundary can be one of [boolean, object]
// but typeof newErrorBoundary === 'object'
// newErrorBoundary should always have the key 'open', which indicates ErrorBoundary on or off
const newErrorBoundary = {
...(typeof nextErrorBoundary === 'boolean'
? { open: nextErrorBoundary }
: nextErrorBoundary),
...(typeof errorBoundary === 'boolean'
? { open: errorBoundary }
: errorBoundary),
};

if (!('open' in newErrorBoundary)) {
newErrorBoundary.open = false;
}

return {
prefix: newPrefix,
locale: newLocale,
pure: newPure,
rtl: newRtl,
warning: nextWarning,
errorBoundary: newErrorBoundary,
};
}
Loading

0 comments on commit ada2b5b

Please sign in to comment.