前言
Dan
的文章在使用React.memo之前的注意事项[1]中,通过几个例子来描述,有时候我们可以通过「组件组合」的方式来优化组件的多余渲染。文章中提到要么通过将「下放State」,要么将「内容提升」。因为组件组合是React
的自然思维模式。正如Dan所指出的,这也将与Server Components非常契合。
然后,在各种文章中,都提倡克制useMemo
的使用,优先使用「组件组合」来处理组件冗余渲染的问题。但是,它们都没讲明白,遇到这些问题,为什么不首选使用React.memo
呢?
最核心的点,就是
❝
Memo
很容易被破坏 ❞
下面,我们就由浅入深的来窥探其中的门道。
前置知识点
❝「前置知识点」,只是做一个概念的介绍,不会做深度解释。因为,这些概念在下面文章中会有出现,为了让行文更加的顺畅,所以将本该在文内的概念解释放到前面来。「如果大家对这些概念熟悉,可以直接忽略」 同时,由于阅读我文章的群体有很多,所以有些知识点可能「我视之若珍宝,尔视只如草芥,弃之如敝履」。以下知识点,请「酌情使用」。 ❞
Object.is
Object.is
是 JavaScript
中的一个「内建函数」,用于比较两个值是否严格相等。它的作用类似于严格相等操作符 ===
,但有一些关键区别。
语法
Object.is(value1, value2)
参数
value1
:比较的第一个值。value2
:比较的第二个值。
返回值
Object.is
返回一个布尔值
,表示两个值是否「严格相等」。
特点
「NaN 相等性:」 Object.is
在比较 NaN
值时与其他方法不同。它认为 Object.is(NaN, NaN)
为 true
,而严格相等操作符 ===
认为 NaN === NaN
为 false
。
Object.is(NaN, NaN); // true
NaN === NaN; // false
「 0 和 -0 不相等:」 Object.is
能够区分正零
和负零
,即 Object.is( 0, -0)
返回 false
,而 ===
会认为它们相等。
Object.is( 0, -0); // false
0 === -0; // true
「 0 和 -0 与0相等:」 除了自身之外,正零和负零都与其他数字相等。
代码语言:javascript复制Object.is( 0, 0); // true
Object.is(-0, 0); // true
「其它值的比较:」 对于其他值,Object.is
表现与 ===
相同。
Object.is(1, 1); // true
Object.is('foo', 'foo'); // true
用途
Object.is
主要用于比较两个值,特别是在需要明确处理 NaN
或区分正零和负零时。这可以用于创建更精确的相等性检查,而不受 JavaScript
中一些奇怪的行为的影响。例如,当比较浮点数或需要区分 NaN
时,Object.is
可能更有用。
function areTheyEqual(value1, value2) {
return Object.is(value1, value2);
}
areTheyEqual(NaN, NaN); // true
areTheyEqual( 0, -0); // false
Record 和Tuple
它们属于ECMAScript
提案Records and Tuples[2]。
Record
(记录):这将是一种「深度不可变」的类对象结构
,与普通JavaScript
对象不同,其属性和值将是不可变的。这将有助于避免对象的属性被无意中更改。Tuple
(元组):这将是一种「深度不可变」的类数组结构
,与普通JavaScript
数组不同,其元素将是不可变的。这将有助于确保元组的内容在创建后不可更改。
这些看起来类似于普通的对象和数组,但它们具有以“#”前缀为特征:
代码语言:javascript复制const record = #{a: 1, b: 2};
record.a;
// 1
const updatedRecord = #{...record, b: 3};
// #{a: 1, b: 3};
const tuple = #[1, 5, 2, 3, 4];
tuple[1];
// 5
const filteredTuple = tuple.filter(num => num > 2);
// #[5, 3, 4];
它们「默认是深度不可变」的:
代码语言:javascript复制const record = #{a: 1, b: 2};
record.b = 3;
// 抛出 TypeError
它们可以被视为「复合基本类型」,并且可以通过值进行比较。
「非常重要」:两个深度相等的Record
将始终使用 ===
运算符返回 true
。
{a: 1, b: [3, 4]} === {a: 1, b: [3, 4]}
// 使用对象 => false
#{a: 1, b: #[3, 4]} === #{a: 1, b: #[3, 4]}
// 使用记录 => true
我们可以认为Record
的变量就是其实际值,类似于常规JS原始类型。
它们与JSON
互操作:
const record = JSON.parseImmutable('{a: 1, b: [2, 3]}');
// #{a: 1, b: #[2, 3]}
JSON.stringify(record);
// '{a: 1, b: [2, 3]}'
它们只能包含其他Record
和Tuple
,或简单数据类型。
const record1 = #{
a: {
regular: 'object'
},
};
// 抛出 TypeError,因为记录不能包含对象
const record2 = #{
b: new Date(),
};
// 抛出 TypeError,因为记录不能包含日期
const record3 = #{
c: new MyClass(),
};
// 抛出 TypeError,因为记录不能包含类
const record4 = #{
d: function () {
alert('forbidden');
},
};
// 抛出 TypeError,因为记录不能包含函数
2. 问题复现
上面提到了 -「Memo很容易被破坏」
简而言之:当React
渲染一个组件树时,它会「从上往下渲染所有子组件」。一旦渲染开始,我们就没有办法停止它。通常情况下,这是一件好事,因为渲染确保我们在屏幕上看到正确的状态反映。此外,渲染通常是快速的。
当然还有那些特殊情况
,它们需要处理一下耗时任务,从而使的渲染变得「步履蹒跚」。同时,由于某些原因,我们都有一些组件(前任留下的