Skip to content

React Route 中 ErrorBoundary 死循环问题 #43

@creamidea

Description

@creamidea

有如下代码,如果此时一个 route 的 element 抛出异常,那么该异常会不断进入 ErrorBoundaryrender 函数进行异常渲染,直至最大调用栈报错。经过一番排出,最终解决方案,只需要对 ErrorBoundary 增加 key 属性即可,我想了解 React 运行机制的小伙伴已经嗅到真相的味道了。

<Routes>
  {routes.map((route, index) => {
    return (
      <Route
        path={route.url}
        element={
          <Suspense fallback="loading...">
            <ErrorBoundary>
              {route.element}
            </ErrorBoundary>
          </Suspense>
        }
      />
    );
  })}
</Routes>

// ErrorBoundary
export class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

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

  componentDidCatch(error, errorInfo) {
    // 你同样可以将错误日志上报给服务器
    console.log('send:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      if (this.state.error.code === 401) {
        return <Navigate to="/login" replace={true} />;
      }
      if (this.state.error.code === 403) {
        return <NoPermission />;
      }
      // 你可以自定义降级后的 UI 并渲染
      return <h1>{this.state.error.message || "Something went wrong"}</h1>;
    }

    return this.props.children; 
  }
}

// 其中一个 route.element
export default function Dashboard() {
  // 通过 axios 网络访问获取数据异常
  throw {
    success: false,
    code: 401,
    message: 'no permission',
  };
}

在 React 的 reconcileChildFibers 阶段,本案例是进入了 reconcileSingleElement 函数逻辑:先比较 key,再比较 elementType,问题就出在这 2 次比较。

展开分析这里之前,我们先了解一下 react router 中 RouteRoutes 这 2 个组件。

从上面得知,虽然我们代码里面使用 map 返回了多个 ErrorBoundary,但其实在 React 里面只有命中当前 URL 的组件(一个或多个,本案例只有一个)。

// 这里 Routes 只是渲染了命中的子组件
<Routes>
  {routes.map((route, index) => {
    return (
      <Route
        path={route.url}
        element={
          <Suspense fallback="loading...">
            <ErrorBoundary>
              {route.element}
            </ErrorBoundary>
          </Suspense>
        }
      />
    );
  })}
</Routes>

现在我们回到 reconcileChildFibers 的比较情况。如果没有设置 key(默认值为 null),null === null 表明 key 是一致的。那么就看 elementType,从 Routes 渲染情况刻制,elementType 也是一致的。于是进入了复用 fiber(ErrorBoundary) 逻辑。这个影响就是 this.state.hasError 始终为 true。那么就会再次进入 <Navigate to="/login" replace={true} /> 逻辑,从而表现为不断循环的现象。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions