浏览器导航因使用 React 错误边界而中断
Browser navigation broken by use of React Error Boundaries
当我们的 React 16 代码库中抛出错误时,它会被我们的顶级错误边界捕获。当发生这种情况时,ErrorBoundary
组件会愉快地呈现一个错误页面。
ErrorBoundary 所在的位置
return (
<Provider store={configureStore()}>
<ErrorBoundary>
<Router history={browserHistory}>{routes}</Router>
</ErrorBoundary>
</Provider>
)
但是,当使用浏览器后退按钮(单击一次)返回时,URL 地址发生变化但页面没有更新。
我已尝试将错误边界向下移动到组件树中,但此问题仍然存在。
关于这个问题出在哪里的任何线索?
操作员现在可能已经找到了解决方案,但为了其他遇到此问题的人的利益,我将解释为什么我认为它会发生以及可以采取哪些措施来解决它。
这可能是由于 ErrorBoundary 中的条件呈现呈现错误消息,即使历史已更改。
虽然上面没有显示,但是ErrorBoundary中的render方法大概是这样的:
render() {
if (this.state.hasError) {
return <h1>An error has occurred.</h1>
}
return this.props.children;
}
在 componentDidCatch
生命周期方法中设置了 hasError。
一旦设置了 ErrorBoundary 中的状态,它将始终呈现错误消息,直到状态发生变化(上例中的 hasError 为 false)。子组件(在本例中为 Router 组件)不会被渲染,即使历史发生变化。
要解决此问题,请使用 react-router withRouter 高阶组件,通过包装 ErrorBoundary 的导出以使其通过道具访问历史记录:
export default withRouter(ErrorBoundary);
在 ErrorBoundary 构造函数中从道具中检索历史记录并设置一个处理程序以使用 history.listen 侦听对当前位置的更改。当位置发生变化(单击后退按钮等)时,如果组件处于错误状态,则会将其清除,从而使子项能够再次呈现。
const { history } = this.props;
history.listen((location, action) => {
if (this.state.hasError) {
this.setState({
hasError: false,
});
}
});
tl;dr 将组件包装在您预期错误的位置,但不是整个树
首先尝试@jdavies 使用 withRouter
回答,但随后为我的用例找到了更好的解决方案:Dan from the React-Team advised against using HOCs with Error Boundaries and rather use them at stragetic places.
在那个 Twitter 帖子中,虽然是关于利弊的辩论,但 Dan 没有公开你应该走哪条路,但我发现他的想法很有说服力。
所以我所做的只是包裹那些 战略位置 我预计会出现错误的地方,而不是整个树。对于我的用例,我更喜欢这个,因为我可以抛出比以前更具表现力、更具体的错误页面(出了点问题 vs 出现了身份验证错误) .
要添加到上述 jdavies 的回答中,请确保您在 componentDidMount
或 useEffect
中注册历史侦听器(使用 []
表示它没有依赖项),并且 在 componentWillUnmount
或 useEffect
return 语句中注销 ,否则你可能 运行 遇到 setState
被调用的问题一个未安装的组件。
示例:
componentDidMount() {
this.unlisten = this.props.history.listen((location, action) => {
if (this.state.hasError) {
this.setState({ hasError: false });
}
});
}
componentWillUnmount() {
this.unlisten();
}
jdavies 评论是必经之路,
但是,如果你对此感到困惑,基本上你会把它看成这样:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
const { history } = props;
history.listen((location, action) => {
if (this.state["hasError"]) {
this.setState({
hasError: false,
});
}
});
this.state = { hasError: false };
}
...
然后在文件末尾添加:
export default withRouter(ErrorBoundary);
(别忘了在顶部 import { withRouter } from "react-router-dom";
)
此外,如果您使用的是export class ErrorBoundry ...
就像我一样,不要忘记将 import { ErrorBoundary } from "./ErrorBoundry";
更改为 import ErrorBoundary from "./ErrorBoundry";
无论你在哪里使用它,例如App.tsx
React Router 6 的 jdavies 答案的模拟是:
const { pathname } = useLocation()
const originalPathname = useRef(pathname)
useEffect(() => {
if (pathname !== originalPathname.current) {
resetErrorBoundary()
}
}, [pathname, resetErrorBoundary])
当我们的 React 16 代码库中抛出错误时,它会被我们的顶级错误边界捕获。当发生这种情况时,ErrorBoundary
组件会愉快地呈现一个错误页面。
ErrorBoundary 所在的位置
return (
<Provider store={configureStore()}>
<ErrorBoundary>
<Router history={browserHistory}>{routes}</Router>
</ErrorBoundary>
</Provider>
)
但是,当使用浏览器后退按钮(单击一次)返回时,URL 地址发生变化但页面没有更新。
我已尝试将错误边界向下移动到组件树中,但此问题仍然存在。
关于这个问题出在哪里的任何线索?
操作员现在可能已经找到了解决方案,但为了其他遇到此问题的人的利益,我将解释为什么我认为它会发生以及可以采取哪些措施来解决它。
这可能是由于 ErrorBoundary 中的条件呈现呈现错误消息,即使历史已更改。
虽然上面没有显示,但是ErrorBoundary中的render方法大概是这样的:
render() {
if (this.state.hasError) {
return <h1>An error has occurred.</h1>
}
return this.props.children;
}
在 componentDidCatch
生命周期方法中设置了 hasError。
一旦设置了 ErrorBoundary 中的状态,它将始终呈现错误消息,直到状态发生变化(上例中的 hasError 为 false)。子组件(在本例中为 Router 组件)不会被渲染,即使历史发生变化。
要解决此问题,请使用 react-router withRouter 高阶组件,通过包装 ErrorBoundary 的导出以使其通过道具访问历史记录:
export default withRouter(ErrorBoundary);
在 ErrorBoundary 构造函数中从道具中检索历史记录并设置一个处理程序以使用 history.listen 侦听对当前位置的更改。当位置发生变化(单击后退按钮等)时,如果组件处于错误状态,则会将其清除,从而使子项能够再次呈现。
const { history } = this.props;
history.listen((location, action) => {
if (this.state.hasError) {
this.setState({
hasError: false,
});
}
});
tl;dr 将组件包装在您预期错误的位置,但不是整个树
首先尝试@jdavies 使用 withRouter
回答,但随后为我的用例找到了更好的解决方案:Dan from the React-Team advised against using HOCs with Error Boundaries and rather use them at stragetic places.
在那个 Twitter 帖子中,虽然是关于利弊的辩论,但 Dan 没有公开你应该走哪条路,但我发现他的想法很有说服力。
所以我所做的只是包裹那些 战略位置 我预计会出现错误的地方,而不是整个树。对于我的用例,我更喜欢这个,因为我可以抛出比以前更具表现力、更具体的错误页面(出了点问题 vs 出现了身份验证错误) .
要添加到上述 jdavies 的回答中,请确保您在 componentDidMount
或 useEffect
中注册历史侦听器(使用 []
表示它没有依赖项),并且 在 componentWillUnmount
或 useEffect
return 语句中注销 ,否则你可能 运行 遇到 setState
被调用的问题一个未安装的组件。
示例:
componentDidMount() {
this.unlisten = this.props.history.listen((location, action) => {
if (this.state.hasError) {
this.setState({ hasError: false });
}
});
}
componentWillUnmount() {
this.unlisten();
}
jdavies 评论是必经之路,
但是,如果你对此感到困惑,基本上你会把它看成这样:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
const { history } = props;
history.listen((location, action) => {
if (this.state["hasError"]) {
this.setState({
hasError: false,
});
}
});
this.state = { hasError: false };
}
...
然后在文件末尾添加:
export default withRouter(ErrorBoundary);
(别忘了在顶部 import { withRouter } from "react-router-dom";
)
此外,如果您使用的是export class ErrorBoundry ...
就像我一样,不要忘记将 import { ErrorBoundary } from "./ErrorBoundry";
更改为 import ErrorBoundary from "./ErrorBoundry";
无论你在哪里使用它,例如App.tsx
React Router 6 的 jdavies 答案的模拟是:
const { pathname } = useLocation()
const originalPathname = useRef(pathname)
useEffect(() => {
if (pathname !== originalPathname.current) {
resetErrorBoundary()
}
}, [pathname, resetErrorBoundary])