错误边界禁用交换机内部的路由

IT技术 reactjs error-handling react-router
2021-05-12 08:17:23

对于很长一段时间我一直在尝试错误的边界后,得到路由到我们的应用程序的工作一直打到,但今天我不仅发现了看似相同的例子很多躺在附近的代码有一个重要的区别:条航线由一个Switch. 如果启用,这个简单的更改足以阻止路由工作。演示

取以下片段。如果我删除该Switch位,即使每个组件都应该失败,这也能正常工作,但如果被开关包裹则不会。我想知道为什么。

<div style={{ backgroundColor: "#ffc993", height: "150px" }}>
<Switch>
  <Route
    path="/"
    exact
    render={() => (
      <ErrorBoundary>
        <MyComponent1 title="Component 1" />
      </ErrorBoundary>
    )}
  />
  <Route
    path="/comp1"
    render={() => (
      <ErrorBoundary>
        <MyComponent1 title="Component 1 Again" />
      </ErrorBoundary>
    )}
  />
  <Route
    path="/comp2"
    render={() => (
      <ErrorBoundary>
        <MyComponent2 title="Component 2" />
      </ErrorBoundary>
    )}
  />
</Switch>
2个回答

基本上,这个问题归结为 React 如何进行reconciliation

当组件更新时,实例保持不变,以便在渲染之间保持状态。React 更新底层组件实例的 props 以匹配新元素

假设我们有这个示例应用程序:

<App>
  <Switch>
    <Route path="a" component={Foo}/>
    <Route path="b" component={Foo}/>
  </Switch>
</App> 

这将有点不直观,Foo为两条路线重用相同的实例A<Switch>将始终返回第一个匹配的元素,因此基本上当 React 渲染时,这相当于<App><Foo/></App>路径“a”和<App><Foo/></App>路径“b”的树如果 Foo 是一个有状态的组件,这意味着状态被保留,因为实例只是传递了新的 props(children在我们的例子中没有,除了),并且预计会通过重新计算它自己的状态来处理这个。

由于我们的错误边界被重用,虽然它的状态无法改变,但它永远不会重新渲染其父路由的新子节点。

React 对此隐藏了一个技巧,我只在其博客中明确记录了这一点:

为了在移动到不同项目时重置该值(如在我们的密码管理器场景中),我们可以使用称为 key 的特殊 React 属性。当一个键发生变化时,React 会创建一个新的组件实例而不是更新当前的组件实例(...) 在大多数情况下,这是处理需要重置的状态的最佳方式。

我首先被一个关于 Brian Vaughn 的错误绑定包的相关问题暗示了这一点

我建议重置此错误边界的方法(如果您真的想消除错误)是使用新的键值将其清除。(...) 这将告诉 React 丢弃前一个实例(带有错误状态)并用新实例替换它。

使用keys的替代方法是实现公开一些可以从外部调用的钩子,或者尝试检查children属性的更改,这很困难。这样的事情可以工作(演示):

componentDidUpdate(prevProps, prevState, snapshot) {
    const childNow = React.Children.only(this.props.children);
    const childPrev = React.Children.only(prevProps.children);

    if (childNow !== childPrev) {
        this.setState({ errorInfo: null });
   }

但它需要更多的工作并且更容易出错,所以为什么要麻烦:坚持添加一个keyprops:-)

要为您提供此修复的快捷方式,请查看每个 ErrorBoundary 组件上的新“key”属性,并且每个组件都必须是唯一的,因此代码应如下所示:

<Switch>
  <Route
    path="/"
    exact
    render={() => (
      <ErrorBoundary key="1">
        <MyComponent1 title="Component 1" />
      </ErrorBoundary>
    )}
  />
  <Route
    path="/comp1"
    render={() => (
      <ErrorBoundary key="2">
        <MyComponent1 title="Component 1 Again" />
      </ErrorBoundary>
    )}
  />
  <Route
    path="/comp2"
    render={() => (
      <ErrorBoundary key="3">
        <MyComponent2 title="Component 2" />
      </ErrorBoundary>
    )}
  />
</Switch>

详细说明,@oligofren 的答案是正确的。这 3 个 ErrorBoundary 组件是相同的实例,但在 props 上可能不同。您可以通过将“id”属性传递给每个 ErrorBoundary 组件来验证这一点。

现在您提到了为什么如果您删除Switch组件,它会按预期工作?由于此代码:https : //github.com/ReactTraining/react-router/blob/e81dfa2d01937969ee3f9b1f33c9ddd319f9e091/packages/react-router/modules/Switch.js#L40

我建议你在这里阅读React.cloneElement的官方文档https : //reactjs.org/docs/react-api.html#cloneelement

我希望这能让你对这个问题有所了解。感谢@oligofren,因为他更详细地解释了这些组件的实例的想法。