-
Notifications
You must be signed in to change notification settings - Fork 4
Open
Description
有如下代码,如果此时一个 route 的 element 抛出异常,那么该异常会不断进入 ErrorBoundary 的 render 函数进行异常渲染,直至最大调用栈报错。经过一番排出,最终解决方案,只需要对 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 中 Route 和 Routes 这 2 个组件。
Route是空组件,仅起到「标识」作用 https://github.com/remix-run/react-router/blob/334589beb3aeedb48ea2f3f05c14b48c5d439b2f/packages/react-router/lib/components.tsx#L144Routes会扫描所有自组件 Route,并转成配置形式给useRoutes处理。最终返回的子组件是匹配到的组件,用 Provider 包裹之后直接返回。https://github.com/remix-run/react-router/blob/334589beb3aeedb48ea2f3f05c14b48c5d439b2f/packages/react-router/lib/hooks.tsx#L385
从上面得知,虽然我们代码里面使用 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
Labels
No labels