返回文章列表
frontend2026年5月28日约 6 分钟阅读

父组件更新,子组件一定会更新吗?

从一道面试题整理 React.memo、useMemo,以及它们到底解决的是什么问题

最近面试里遇到这个问题:

父组件中引用了一个子组件,父组件刷新了,子组件会刷新么?用什么来阻止?useMemo 和 React.memo 的区别是什么?

这个问题看起来很简单,但其实很容易答乱。因为里面同时涉及了三个概念:

  • 父组件重新渲染
  • 子组件是否重新渲染
  • props 的引用是否稳定

先看一段代码:

const Child = React.memo(({ obj }) => {
  console.log('child render')
 
  return <div>{obj.a}</div>
})
 
function Parent() {
  const [count, setCount] = useState(0)
 
  const obj = useMemo(() => {
    return { a: 1 }
  }, [])
 
  return (
    <>
      <button onClick={() => setCount(count + 1)}>
        add
      </button>
 
      <Child obj={obj} />
    </>
  )
}

点击按钮后,count 改变,Parent 会重新渲染。

那 Child 会不会重新渲染?

答案是:不会。

因为这里同时做了两件事:

  • Child 被 React.memo 包裹了
  • 传给 Child 的 obj 被 useMemo 缓存了,引用没有变

所以父组件更新时,React 会重新执行 Parent 函数,但检查到 Child 的 props 没变,就会跳过 Child 的重新渲染。


父组件更新,子组件默认会不会更新?

默认情况下:会。

函数组件的渲染,本质上就是重新执行函数。

function Parent() {
  const [count, setCount] = useState(0)
 
  return (
    <>
      <button onClick={() => setCount(count + 1)}>
        add
      </button>
 
      <Child />
    </>
  )
}

当 setCount 执行后:

  • Parent 的 state 变化
  • Parent 重新执行
  • JSX 重新生成
  • 子组件默认也会跟着进入渲染流程

所以可以先记住一句话:

父组件重新渲染时,子组件默认也会重新渲染。

这个“重新渲染”不一定代表真实 DOM 一定会更新。React 后面还会做 diff,如果内容没变化,真实 DOM 可能不会变。

但是组件函数是否执行,是另一个问题。


用什么阻止子组件重新渲染?

通常用 React.memo。

const Child = React.memo(function Child({ name }) {
  console.log('child render')
  return <div>{name}</div>
})

React.memo 的作用是:

当父组件重新渲染时,如果子组件的 props 没有变化,就跳过子组件本身的重新渲染。

它比较的是 props。

<Child name="Elemen" />

如果 name 还是 "Elemen",那么 Child 可以不重新执行。

但是这里有一个很重要的点:React.memo 默认是浅比较。

也就是说:

  • 基本类型比较值
  • 对象、数组、函数比较引用

为什么只用 React.memo 还不够?

看这个例子:

const Child = React.memo(({ obj }) => {
  console.log('child render')
  return <div>{obj.a}</div>
})
 
function Parent() {
  const [count, setCount] = useState(0)
 
  const obj = { a: 1 }
 
  return (
    <>
      <button onClick={() => setCount(count + 1)}>
        add
      </button>
 
      <Child obj={obj} />
    </>
  )
}

这时候点击按钮,Child 还是会重新渲染。

原因是:

const obj = { a: 1 }

每次 Parent 重新执行时,都会创建一个新的对象。

虽然内容看起来都是 { a: 1 },但是引用地址变了。

{ a: 1 } === { a: 1 } // false

所以在 React.memo 看来:

prevProps.obj !== nextProps.obj

props 变了,子组件就会重新渲染。

这也是为什么很多时候你明明用了 React.memo,但是发现没有效果。


useMemo 在这里解决了什么?

useMemo 用来缓存一个值。

const obj = useMemo(() => {
  return { a: 1 }
}, [])

依赖数组是空数组,表示这个对象只在组件第一次渲染时创建一次。

后面 Parent 因为 count 改变而重新渲染时,obj 还是之前那个对象引用。

所以:

prevProps.obj === nextProps.obj

这时候 React.memo 才能判断出 props 没有变化,然后跳过 Child 的重新渲染。

也就是说,这道题里的关键不是只用了 React.memo,而是:

React.memo + useMemo

一个负责缓存组件渲染结果,一个负责稳定传入的对象引用。


useMemo 和 React.memo 的区别

这两个名字很像,但它们不是同一层面的东西。

React.memo

React.memo 是包组件的。

const Child = React.memo(ChildComponent)

它的目标是:

让组件在 props 没变时不要重新渲染。

它关注的是:组件要不要重新执行。

适合用在:

  • 子组件渲染成本比较高
  • 子组件 props 不经常变化
  • 父组件更新很频繁

useMemo

useMemo 是写在组件内部的 Hook。

const value = useMemo(() => {
  return expensiveCalc(data)
}, [data])

它的目标是:

缓存一个计算结果或引用类型的值。

它关注的是:这个值要不要重新计算 / 重新创建。

适合用在:

  • 复杂计算
  • 过滤、排序、统计等比较耗时的逻辑
  • 缓存对象、数组,避免引用每次都变
  • 配合 React.memo 稳定子组件 props

可以简单理解为:

API缓存的是什么用在哪里解决的问题
React.memo组件组件外面props 没变时,跳过子组件渲染
useMemo值组件里面依赖没变时,复用上一次的计算结果

再补一个函数的情况

如果传给子组件的是函数,也会遇到类似问题。

const Child = React.memo(({ onClick }) => {
  console.log('child render')
  return <button onClick={onClick}>child button</button>
})
 
function Parent() {
  const [count, setCount] = useState(0)
 
  const handleClick = () => {
    console.log('click')
  }
 
  return (
    <>
      <button onClick={() => setCount(count + 1)}>
        add
      </button>
 
      <Child onClick={handleClick} />
    </>
  )
}

每次 Parent 重新渲染时,handleClick 都是一个新函数。

所以 Child 还是会重新渲染。

这种情况一般用 useCallback:

const handleClick = useCallback(() => {
  console.log('click')
}, [])

useCallback 可以理解为专门缓存函数引用的 useMemo。

const handleClick = useCallback(() => {}, [])
 
// 大致等价于
const handleClick = useMemo(() => {
  return () => {}
}, [])

所以常见组合是:

  • 传对象 / 数组:React.memo + useMemo
  • 传函数:React.memo + useCallback

回到面试题

如果面试官问:

父组件刷新了,子组件会刷新么?

可以这样答:

默认情况下会。父组件 state 改变后会重新执行组件函数,子组件也会跟着进入渲染流程。但是这不一定代表真实 DOM 一定更新,React 还会做 diff。

如果想阻止子组件不必要的重新渲染,可以用 React.memo 包裹子组件。React.memo 会对 props 做浅比较,如果 props 没变,就跳过子组件渲染。

但如果传给子组件的是对象、数组、函数,就要注意引用问题。因为父组件每次重新执行都会重新创建这些引用类型,导致 React.memo 判断 props 变化。

所以对象、数组可以用 useMemo 缓存,函数可以用 useCallback 缓存。

结合题目中的代码:

const Child = React.memo(({ obj }) => {
  console.log('child render')
  return <div>{obj.a}</div>
})
 
const obj = useMemo(() => {
  return { a: 1 }
}, [])

Child 被 React.memo 包裹,obj 又被 useMemo 缓存,所以点击 add 只会让 Parent 重新渲染,Child 不会重新渲染。


总结

这道题的核心其实是:React 组件重新渲染和 props 引用变化之间的关系。

几个结论:

  • 父组件重新渲染时,子组件默认也会重新渲染
  • React.memo 可以在 props 没变时跳过子组件渲染
  • React.memo 默认只做 props 浅比较
  • 对象、数组、函数每次重新创建都会产生新引用
  • useMemo 用来缓存值,常用于稳定对象和数组引用
  • useCallback 用来缓存函数引用
  • React.memo 缓存的是组件,useMemo 缓存的是值

所以不要把 React.memo 和 useMemo 理解成同一个东西。

一个是在组件外面控制“组件要不要重新渲染”,一个是在组件里面控制“值要不要重新计算 / 重新创建”。

目录 · 收起