一、依赖项的比较是浅比较
深比较的性能消耗特别大,深比较需要递归遍历对象 / 数组的每一层属性,直到所有基本类型值都对比完成。
而浅比较(Object.is)的成本是 O (1):仅对比引用或基本类型值,几乎无性能损耗,能保证渲染流程的高效性。
二、核心原理:Object.is 与依赖项比较
1. Object.is 的比较规则
Object.is 是 ES6 新增的等值判断方法,规则介于 === 和普通相等之间:
- 和
===一致:严格区分类型(比如1 !== '1',Object.is(1, '1') // false); - 和
===不同:修复了两个特殊场景:Object.is(NaN, NaN) // true(而NaN === NaN // false);Object.is(+0, -0) // false(而+0 === -0 // true)。
2. React 如何用 Object.is 比较依赖项
当组件重新渲染时,React 会:
- 保存上一次渲染时依赖数组的“快照”(比如
[a, b]); - 用
Object.is逐个对比新/旧依赖项:- 若所有依赖项都满足
Object.is(新值, 旧值) === true→ 不执行useEffect回调; - 若任意一个依赖项不满足 → 执行清理函数(若有),再执行新的
useEffect回调。
- 若所有依赖项都满足
三、常见误区:引用类型的依赖比较
需要特别注意:Object.is 对引用类型(对象、数组、函数)的比较是引用比较(和 === 一致),而非深比较:
jsx
import { useEffect, useState } from 'react';
function Demo() {
const [obj, setObj] = useState({ name: 'test' });
// 每次渲染都会创建新对象,Object.is(新对象, 旧对象) → false,每次都执行
useEffect(() => {
console.log('obj 变化');
}, [obj]);
return (
<button onClick={() => setObj({ name: 'test' })}>
赋值相同内容的新对象
</button>
);
}
问题:
即使对象内容完全相同,只要是新创建的引用,Object.is 就会判定为“变化”,导致 useEffect 每次渲染都执行。
解决:
- 对对象/数组:使用
useMemo缓存,只在内容变化时更新引用; - 对函数:使用
useCallback缓存,只在依赖变化时更新引用。
四、和 === 的核心区别(React 选择 Object.is 的原因)
React 选择 Object.is 而非 ===,核心是为了处理 NaN 场景:
- 若用
===:依赖项为NaN时,每次渲染都会判定为“变化”(因为NaN === NaN // false),导致useEffect无意义地重复执行; - 用
Object.is:NaN被判定为“未变化”,符合开发者的预期。
总结
useEffect的依赖项比较使用Object.is算法,而非普通的===;Object.is相比===,修复了NaN和+0/-0的判断逻辑,更符合 React 依赖比较的需求;- 对引用类型,
Object.is仍为引用比较,需用useMemo/useCallback缓存避免不必要的重执行。