【翻译】React 重新渲染

22 阅读4分钟

原文链接:shramko.dev/blog/react-…

你好,React Andy

我使用React已有六年多。我注意到,即使是经验丰富的程序员,也常常难以理解React背后的运作机制。这可能导致效率低下,影响代码质量、运行速度和用户体验。

React的官方文档对入门者非常友好,市面上也有大量面向初学者的书籍、课程和博客。但进阶之路何在?如何深入理解其核心机制?若你已具备一定React开发经验,基础甚至中级课程可能难以满足需求。遗憾的是,针对高级学习者的资源极为有限。

为此,我将推出系列文章填补这一知识空白。

重新渲染入门

理解React中的重新渲染对性能至关重要。你需要了解触发重新渲染的条件,掌握它们在应用中的传播路径,并了解重新渲染过程中发生的变化及其重要性。

问题

假设你是某家FAANG公司的实习生。你的首个任务是创建一个新的React组件。需要添加一个简单的 button,点击后会在应用顶部弹出 model dialog

const App = () => {
  // Some code here
  return <div className="layout">
    {/* The button should go somewhere here */}
    <VerySlowComponent />
    <AnotherComponent />
    <OtherStuff />
  </div>;
};

然后你开始实现它。这项任务看似微不足道。我们都做过数百次:

const App = () => {
  // Add state  
  const [isOpen, setIsOpen] = useState(false);

  // Everything that is returned here will be re-rendered when the state is updated
  return <div>
    {/* Add the button */}
    <Button onClick={() => setIsOpen(true)}>
      Open dialog
    </Button>

    {/* Add the dialog */}
    {isOpen && <ModalDialog onClose={() => setIsOpen(false)} />}

    <VerySlowComponent />
    <AnotherComponent />
    <OtherStuff />
  </div>;
};

你添加状态来追踪对话框的打开或关闭状态。你包含一个按钮来改变状态,并根据该状态渲染对话框。

运行应用时,对话框出现前存在明显的延迟⏰,几乎长达一秒

经验丰富的React开发者可能会立刻建议。

"你正在重新渲染整个应用。只需将所有内容包裹在 React.memo 中,并使用 useCallback 避免不必要的渲染。"

这条建议有其合理之处,但切勿贸然采用。在此情境下,备忘化并非必要,反而可能损害性能。

首先,让我们深入剖析具体发生的情况以及延迟产生的原因。

点击按钮时,我们会触发 setIsOpen 设置函数,将 isOpen 状态从 false 更新为 true。因此,持有该状态的 App 组件会重新渲染自身。

状态更新且 App 组件重新渲染后,React 必须将新数据传递给其他依赖组件。它会自动重新渲染首个组件所显示的所有组件,并沿着组件树持续向下遍历直至末端。

若将典型 React 应用可视化为树状结构,你会发现状态更新点以下的所有组件都将被重新渲染。

在React中理解重新渲染至关重要。

需要牢记的关键点是:React 从不会沿渲染树向上重新渲染组件。如果组件树中间层发生状态更新,只有树的下方组件才会被重新渲染。

当组件被 React.memo 包裹时,React 会中断其默认的重新渲染流程,首先评估 props 是否发生变化。若 props 未发生变化,则停止重新渲染;但只要有任何一个 props 被修改,重新渲染就会照常进行。

需要注意的是,通过备忘录机制有效阻止重渲染是一个涉及诸多考量因素的复杂话题。若想深入理解,建议通过新文章进一步探索这些概念。(即将推出...或阅读关于元素、子元素作为 props 以及重渲染的相关内容

使用 React.memo 包裹组件确实能在某些场景下避免不必要的重新渲染。但需要注意的是,React.memo 的使用本身存在复杂性和注意事项,这些将在后续文章中探讨。更有效的方法是将依赖特定状态的组件进行隔离,并将状态与这些组件封装到更小、更专用的组件中。这既能提升性能,又能使组件结构更清晰。

状态下移

让我们将逻辑移至独立组件中。

const ButtonWithModalDialog = () => {
  const [isOpen, setIsOpen] = useState(false);

  return <>
    <Button onClick={() => setIsOpen(true)}>
      Open dialog
    </Button>

    {isOpen && <ModalDialog onClose={() => setIsOpen(false)} />}
  </>;
};

然后在 App 程序中渲染这个新组件:

const App = () => {
  return <div>
    // Our new component
    <ButtonWithModalDialog />

    <VerySlowComponent />
    <AnotherComponent />
    <OtherStuffComponent />
  </div>;
};

因此,模态对话框会立即弹出。我们通过一种简单的组合技术解决了重大性能问题!