【React进阶系列第三课】组件函数什么时候被执行

前面说过,React 每次渲染组件,都会执行组件函数。现在,我们来看看,React 会在什么时候渲染组件,也就是什么时候会执行你的组件函数。

组件函数执行的 3 种情况

你的组件函数会在以下 3 种情况下执行:

  1. 组件首次挂载时
  2. 组件的状态或依赖的 Context 发生变化时
  3. 父组件重新渲染时

第 3 种情况,有必要单独说一下。当 React 更新一个组件的时候,会连带者更新它的子组件,即使子组件的 props 没有发生变化。也就是说,React 更新的是整个组件树,而不是单个组件。

有个普遍的错误说法是 "props 变化时,组件会更新"。第一,props 变化,意味着父组件更新了,此时组件更新是因为父组件更新导致的,不管 props 有没有变化,组件都会更新。其次,如果按这个理解,那你说下面这种情况组件会更新吗?

jsx 复制代码
import { createRoot } from "react-dom/client";

let sec = 0;
setInterval(() => {
  sec++;
}, 1000);

function Timer({ secPassed }) {
  return <p>Seconds passed: {secPassed}</p>;
}

createRoot(document.getElementById("root")).render(<Timer secPassed={sec} />);

性能问题

你可能发现了,父组件更新,所有的后代组件也会一起更新,那性能是不是会有问题?尤其是对那些 props 没有变化的后端组件,执行它们不是浪费吗?

从性能上看这确实是一个浪费。不过,实践证明,在大部分项目下,这种性能损耗不会导致明显的性能问题,不然 React 已经被喷惨了。所以,我们平时不用担心性能问题。

不会引发性能问题,很大一个原因是因为大部分的组件函数都很简单,很快就执行完了。一般的组件函数都是做一些变量的声明、读取、赋值,然后创建并返回 React 元素(JSX)。

这也告诉我们,尽量让组件函数简单,把一些具有较高复杂度的计算放在 useMemo 中,避免不必要的计算。

另一个需要关注的是组件的数量,比如:

  • 大量使用某个组件。比如,展示大量数据,每个数据对应一个 <ListItem />
  • 一个组件有大量的后代组件,而且它自身频繁更新。

这类情况,在每次更新时,都会执行大量的组件函数,容易导致性能问题。可以使用 React.memo,当组件的 props 没有变化时,跳过组件的更新。

React.memo

memo 可以让我们在更新前,比较新旧两次的 props,决定要不要执行更新。

js 复制代码
const MemoizedComponent = memo(SomeComponent, arePropsEqual?)

默认情况下,是对每个 prop 做浅比较,如果存在对象结构的 prop,一般使用 lodash.isEqual 来做深比较,如果含有函数,还需要注意保证先后两次函数的引用不发生变化,不然即使 lodash.isEqual 也会返回 false

memo 优化的是第 3 种情况,也就是父组件更新都会导致子组件更新的情况。对于第 1、2 种情况,组件更新是不能也不可避免的。

memo 可以提升性能,但是,不要滥用,除非你有理由认为这个组件的频繁更新会导致性能问题。

最后,应该在组件使用的时候,才决定要不要使用 memo,不要在组件定义的时候就使用 memo。假如你对外提供组件,你不需要在导出之前使用 memo。因为你写组件的时候,不知道父组件会不会频繁更新,也不知道组件会不会被大量使用,也不知道如何有效比较两次 props,这些只有使用的时候才知道。

总结

  • 函数组件会在 3 中情况下执行:
    1. 组件首次挂载时
    2. 组件的状态或依赖的 Context 发生变化时
    3. 父组件重新渲染时
  • 父组件更新时,子组件默认都会更新,即使子组件的 props 没有变化。
  • 这种冗余的更新,在大部分情况下,不会导致明显的性能问题。
  • 为了避免性能问题,记住:
    • 尽量保持组件简单。
    • 在组件内如果有高复杂度的计算,使用 useMemo
    • 对于大量使用的组件,或者更新繁重的组件,可以在必要时使用 React.memo
  • 组件自身的状态或依赖的 Context 变化时,组件一定会更新,React.memo 不会影响这种情况。
相关推荐
GinoWi15 分钟前
HTML基本格式 - 第一个HTML网页
前端
顶鲜花的牛粪18 分钟前
Astro 项目升级全栈:EdgeOne Pages 部署指南
前端
0***R51529 分钟前
前端云原生
前端·云原生
月弦笙音39 分钟前
【Promise.withResolvers】发现这个api还挺有用
前端·javascript·typescript
疯狂踩坑人41 分钟前
MCP理论和实战,然后做个MCP脚手架吧
前端·node.js·mcp
中杯可乐多加冰1 小时前
基于 DeepSeek + MateChat 的证券智能投顾技术实践:打造金融领域的专属大Q模型助手
前端·人工智能
凡人程序员1 小时前
搭建简易版monorepo + turborepo
前端·javascript
丸子哥哥1 小时前
同一个域名,如何添加多个网站?
服务器·前端·nginx·微服务
不努力也不会混1 小时前
vite联邦实现微前端(vite-plugin-federation)
前端·vue.js
伍亿伍千万1 小时前
Uptime Kuma修改作为内嵌页面的自适应
前端