设计动机

白屏场景:

  • 前端边缘场景导致的代码错误
  • 沟通没有对称,后端数据结构的突变导致代码层报错
  • 后端部署服务导致的接口不可用进而产生的前端报错

解决方案:

为了提升用户体验上的友好,增加ErrorBoundaryUI层降级当页面发生报错的时候,用户依然可以点击菜单栏进行模块切换,无需刷新页面或者重新打开路由,防止客户暴躁的情况出现

具体实现:

  1. 增加ErrorBoundary组件,用于捕获错误,将其包裹在渲染非导航栏渲染位置
  2. 记录报错路由,当用户重新点击其他模块时与错误路由比较,如果不一致,取消错误,重新请求页面
  3. 渲染遇到错误时对应需要展示的组件

ErrorBoundary

ErrorBoundary.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, pathname: props.location.pathname };
}

static getDerivedStateFromError() {
// 更新 state 使下一次渲染能够显示降级后的 UI
return { hasError: true };
}

componentDidUpdate(nextProps, nextState) {
// 如果路由不相等, 并且是之前有错误的, 取消错误, 重新展示正常的页面
if (nextProps.history.location.pathname !== nextState.pathname && this.state.hasError) {
// eslint-disable-next-line react/no-did-update-set-state
this.setState({
hasError: false,
});
}
}


componentDidCatch(error) {
this.setState({
pathname: this.props.location.pathname, // 设置对应的错误路由
});
console.log(error);
// 你同样可以将错误日志上报给服务器
// logErrorToMyService(error, errorInfo);
}

render() {
if (this.state.hasError) {
// 你可以自定义降级后的 UI 并渲染
return <ErrorUI />;
}

return this.props.children;
}
}

export default withRouter(ErrorBoundary);

注意点:

  1. componentDidUpdate中获取最新路由的时候需要从nextProps中路由对象history中获取,而非locationlocation获取到的并非最新的路由,而且跳转前当前的路由
  2. componentDidCatch用户捕获错误,方法可以返回error,可以在其进行错误上报,未来可扩展

Layout.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<Layout>
// 顶部导航
<Navigation
collapsed={collapsed}
customExtraItem={customExtraItem}
customNavigation={customNavigation}
logo={logo}
pathArr={pathArr}
toggleCollapsed={toggleCollapsed}
{...navigation}
/>
<Layout className={styles['section']}>
// 侧边导航
{
!noSider &&
<SideMenu
collapsed={collapsed}
customSideMenu={customSideMenu}
customSideMenuItem={customSideMenuItem}
getBadgeCount={getBadgeCount}
keyMap={keyMap}
menu={menu}
pathArr={pathArr}
toggleCollapsed={toggleCollapsed}
/>
}
// 主体内容
<div className={styles['content']}>
// 错误边界包裹位置
<ErrorBoundary>
{children}
</ErrorBoundary>
</div>
</Layout>
</Layout>

效果展示

ErrorBoundary效果.png