React router 6 卸载时的副作用清理

React router 6 side-effect clean-up on unmount

迁移到 v6 后,我的 react-router 出现问题。问题是当路由器用于渲染组件时会产生副作用并且应该在卸载时进行清理。在 v6 的情况下,我没有让组件 运行 它的清理功能。我假设这是设计使然,但我想知道 how/can 我在 v6 中对 v5 逻辑建模。

我有两个最小的应用程序可以显示我的问题:

在这两个应用程序中,想法是当您导航到页面时,它会呈现一个组件,该组件修改 DOM 并删除它在卸载时添加的 DOM 元素。这在 v6 的情况下不会发生,添加的 DOM 元素保留在 DOM 中并且它们开始堆叠:

useEffect 具有依赖项应该 运行 在渲染后进行清理,所以这实际上是正确的行为。

为了运行卸载时的清理,使用useEffect和一个空的依赖数组

React.useEffect(() => {
    // Side effect where we modify dom
    console.log('modifying: ', name);
    const element = document.createElement('span');
    element.innerText = `This text was added as a side-effect for component: ${name}`;
    const container = document.getElementById(name);
    container?.appendChild(element);

    // Clean-up on unmount
    return () => {
      console.log('unmounting: ', name);
      const container = document.getElementById(name);
      container?.parentNode?.removeChild(container);
    };
  }, []);

但是如果你想实现与 react-router v5 相同的(无效的)行为,你必须强制路由器在每次其 prop 更改时不必要地重新安装相同的组件。

不推荐这样做,但你可以这样做:

const pages = [...Array(3).keys()].map((x) => (
    <Route
      key={`route-${x}`}
      path={`component-${x}`}
      element={<Component key={`component-${x}`} name={`component-${x}`} />}
    />
  ));

(唯一的 key 道具将强制做出反应以不重用相同的组件)

useEffect的解释:

  1. useEffect 渲染后总是 运行s
  2. previous useEffect 总是在 运行ning new useEffect
  3. 之前清理

所以首先是渲染,然后useEffect有新的依赖值想要运行,所以之前的useEffect有旧的值必须清理。

例子

interface FooProps {
  id: string
}

const Foo: React.FC<FooProps> = ({ id }) => {
  React.useEffect(() => {
    console.log({ id })

    return () => {
      console.log({ id })
    }
  }, [id]);

  return <div id={id} />
}

使用该组件时的事件顺序:

  1. id 开始为 'a'
  2. 呈现 ID 为 'a'
  3. div
  4. useEffect 运行s 其依赖项 id 设置为 'a'
  5. Foo 收到 属性 id,值更改为 'b'
  6. 呈现 ID 为 'b'
  7. div 而不是 ID 为 'a'
  8. 的 div
  9. useEffect 运行s 清理,其依赖项 id 设置为 'a'
  10. useEffect 运行s 依赖性 id 设置为 'b'

因此,为了使这一点绝对清楚:

useEffect(() => {
  return () => {
    // This cleanup runs only on unmount
  }
}, []);

useEffect(() => {
  return () => {
    // This cleanup runs right before this useEffect runs with updated name value
  }
}, [name]);

请不要 Imperative 使用 React Declarative 结构,它有时可能会像这样咬你,(当然,命令式我的意思是在 useEffect 中添加 innerText) .

这里需要注意的几点是:

  • 这与 react-router 你使用的 React useEffect 无关,它称你 effectsclean up 功能相同(如您可以通过检查 console 并看到 modifyingunmounting 在两个项目之间是相同的。

  • 第二点是你的cleanup函数,除了console.log('unmounting: ', name);,它什么都不做,因为在这两个项目中container都是null.

const container = document.getElementById(name); //container is null
container?.parentNode?.removeChild(container);

这两个项目的不同之处在于,在 v6 中我们使用 Outlet 组件,并且它保留了您强制添加的 text

但如果您要使用任何其他需要 unsubscribecleanupeffect,您可以在两个版本中完美地完成它。

总而言之,两个版本都可以正常工作,但是您添加或删除文本的方式是错误的,您可以尝试其他方式,例如根据更 Reacty 的状态显示或隐藏文本