React Memo不是你优化的第一选择

2023-10-23 11:56:16 浏览数 (1)

前言

Dan的文章在使用React.memo之前的注意事项[1]中,通过几个例子来描述,有时候我们可以通过「组件组合」的方式来优化组件的多余渲染。文章中提到要么通过将「下放State」,要么将「内容提升」。因为组件组合是React的自然思维模式。正如Dan所指出的,这也将与Server Components非常契合。

然后,在各种文章中,都提倡克制useMemo的使用,优先使用「组件组合」来处理组件冗余渲染的问题。但是,它们都没讲明白,遇到这些问题,为什么不首选使用React.memo呢?

最核心的点,就是

Memo很容易被破坏 ❞

下面,我们就由浅入深的来窥探其中的门道。

前置知识点

「前置知识点」,只是做一个概念的介绍,不会做深度解释。因为,这些概念在下面文章中会有出现,为了让行文更加的顺畅,所以将本该在文内的概念解释放到前面来。「如果大家对这些概念熟悉,可以直接忽略」 同时,由于阅读我文章的群体有很多,所以有些知识点可能「我视之若珍宝,尔视只如草芥,弃之如敝履」。以下知识点,请「酌情使用」。 ❞

Object.is

Object.isJavaScript 中的一个「内建函数」,用于比较两个值是否严格相等。它的作用类似于严格相等操作符 ===,但有一些关键区别。

语法

Object.is(value1, value2)

参数

  • value1:比较的第一个值。
  • value2:比较的第二个值。

返回值

Object.is 返回一个布尔值,表示两个值是否「严格相等」

特点

「NaN 相等性:」 Object.is 在比较 NaN 值时与其他方法不同。它认为 Object.is(NaN, NaN)true,而严格相等操作符 === 认为 NaN === NaNfalse

代码语言:javascript复制
Object.is(NaN, NaN); // true
NaN === NaN; // false

「 0 和 -0 不相等:」 Object.is 能够区分正零负零,即 Object.is( 0, -0) 返回 false,而 === 会认为它们相等。

代码语言:javascript复制
Object.is( 0, -0); // false
 0 === -0; // true

「 0 和 -0 与0相等:」 除了自身之外,正零和负零都与其他数字相等。

代码语言:javascript复制
Object.is( 0, 0); // true
Object.is(-0, 0); // true

「其它值的比较:」 对于其他值,Object.is 表现与 === 相同。

代码语言:javascript复制
Object.is(1, 1); // true
Object.is('foo', 'foo'); // true

用途

Object.is 主要用于比较两个值,特别是在需要明确处理 NaN 或区分正零和负零时。这可以用于创建更精确的相等性检查,而不受 JavaScript 中一些奇怪的行为的影响。例如,当比较浮点数或需要区分 NaN 时,Object.is 可能更有用。

代码语言:javascript复制
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

代码语言:javascript复制
{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互操作:

代码语言:javascript复制
const record = JSON.parseImmutable('{a: 1, b: [2, 3]}');
// #{a: 1, b: #[2, 3]}
JSON.stringify(record);
// '{a: 1, b: [2, 3]}'

它们只能包含其他RecordTuple,或简单数据类型。

代码语言:javascript复制
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渲染一个组件树时,它会「从上往下渲染所有子组件」。一旦渲染开始,我们就没有办法停止它。通常情况下,这是一件好事,因为渲染确保我们在屏幕上看到正确的状态反映。此外,渲染通常是快速的。

当然还有那些特殊情况,它们需要处理一下耗时任务,从而使的渲染变得「步履蹒跚」。同时,由于某些原因,我们都有一些组件(前任留下的

0 人点赞