【问题标题】:React hooks cause unintentional re-renderReact 钩子会导致无意的重新渲染
【发布时间】:2022-01-17 14:54:21
【问题描述】:

我正在使用 React + Leaflet 地图,并注意到地图容器/元素在我显示或关闭模式时重新渲染。

我已经创建了一个沙盒来演示这个问题,但是如果其他示例有帮助的话,我的应用程序中的其他地方也会出现这种情况。

https://codesandbox.io/s/elegant-rain-01f9l?file=/src/App.js

通过阅读替代 SO 帖子,例如 this very similar one,我认识到这可能是由于我的钩子(例如 handleShow/setShow)导致整个组件重新渲染。意外行为如下所示:

如果您拖动地图以使当前位置不在视野范围内并打开模式,我们会强制重新加载<LeafletControlGeocoder/><LocationMarker/>。这很明显,因为地图会重新平移到当前位置,并且地图右上角会出现一个新的“搜索图标”。

复制步骤:

*如果您在沙盒中发现与 react-bootstrap/Modal 相关的问题,只需将依赖项更新为最新(刷新图标) - 这是一个奇怪的沙盒问题,但与我的问题无关。

  1. 拖动地图,使当前位置不在视野范围内
  2. 点击菜单图标(右上角)> 添加位置
  3. 当模态出现时,通知地图重新居中到当前位置并出现另一个搜索图标。

【问题讨论】:

  • 不用担心 react 会重新渲染。而是关注 DOM 是否更新以及是否会导致性能问题。尽量不要用memo等预优化代码
  • 嗨 - 感谢您的关注。这确实会导致 DOM 更新,并且除了性能问题之外还会导致树中出现意外元素。我可以更改我的代码以防止在地图上追加/重新平移,但这似乎是隐藏问题的创可贴
  • 行为是正确的,每次你调用setState或者在这种情况下setValue,它都会重新渲染你的DOM,这是一个React行为,但是,这里的问题是,你组件内不应有组件。您应该将它们拆分为单独的文件,您当前的架构会导致问题,基于 React 生命周期,因为它每次都会重新渲染,每次都会重新生成函数。所以,请尝试将您的LeafletControlGeocoder 拆分为一个文件,它应该可以正常工作。
  • 尽量不要把所有东西都放在App,提取你的模型,也许叫它<Models/>,然后用这个保存状态。如果你把你的应用细分成,React 会更好更小的组件。
  • @EsdrasXavier "您应该将它们拆分为单独的文件" 实际上这不是必需的,您可以在同一个文件中包含多个组件。然而,关键确实是不要在另一个身体内有一些组件......

标签: javascript reactjs react-hooks leaflet react-leaflet


【解决方案1】:

问题是由于在另一个 React 功能组件的主体中定义了一些子组件函数

function App() { // A React functional component
  const [show, setShow] = useState(false);
  // etc.

  // A child React functional component in the body of another component...
  function LocationMarker() {
    const [position, setPosition] = useState(null);
    // etc.
  }

  // Another child component
  function LeafletControlGeocoder() {
    useEffect(() => {}, []);
    // etc.
  }

  return (
    <>
      {/* More content... */}
      <MapContainer center={[45.4, -75.7]} zoom={12}>
        <LeafletControlGeocoder />
        <LocationMarker />
      </MapContainer>
    </>
  );
}

如果您的子组件是无状态的(没有挂钩),这可能不会有问题。在这种情况下,它们更像是“子模板”。

但是如果这些子组件确实使用了一些钩子,那么在另一个功能组件的主体中会干扰钩子的工作方式:因为子组件函数在每次重新渲染时都会重新创建,React 很难识别那些children 是相同的组件,并最终复制它们的输出。

只需确保不要在另一个功能组件中定义某些功能组件,而是始终在范围的顶层(通常在文件的根目录)。如问题 cmets 中所述,标准做法是每个文件有 1 个组件。

function App() { // A React functional component
  const [show, setShow] = useState(false);
  // etc.

  return (
    <>
      {/* More content... */}
      <MapContainer center={[45.4, -75.7]} zoom={12}>
        <LeafletControlGeocoder />
        <LocationMarker />
      </MapContainer>
    </>
  );
}

// Another React functional component at top scope level
function LocationMarker() {
  const [position, setPosition] = useState(null);
  // etc.
}

// Another component
function LeafletControlGeocoder() {
  useEffect(() => {}, []);
  // etc.
}

固定应用:https://codesandbox.io/s/lively-monad-yve67

【讨论】:

  • 这行得通 - 非常感谢 ghybs 不仅提供答案,还感谢概念背景 - 在我继续学习 React 时,这对我理解非常有帮助。我已经阅读了暗示您的观点的文档,但是在您发布之前无法理解该应用程序。非常感谢您的宝贵时间!
猜你喜欢
  • 1970-01-01
  • 2020-07-26
  • 2021-04-23
  • 1970-01-01
  • 2021-06-19
  • 2023-03-25
  • 1970-01-01
  • 2020-10-29
  • 2020-01-26
相关资源
最近更新 更多