浏览器导航因使用 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 的回答中,请确保您在 componentDidMountuseEffect 中注册历史侦听器(使用 [] 表示它没有依赖项),并且 componentWillUnmountuseEffect 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])