基本的な使い方
useMemo は React のフック関数であり、不必要な繰り返し計算を避けるために計算結果をキャッシュするために使用されます。関数と依存関係の配列をパラメータとして受け取り、キャッシュされた値を返します。依存配列内の値が変更されると、useMemo は再計算して、キャッシュされた新しい値を返します。
useMemo の基本的な構文は次のとおりです。
import React, { useMemo } from 'react';
const MyComponent = () => {
const memoizedValue = useMemo(() => {
// 执行一些计算或逻辑
// 返回计算结果
}, [dependency1, dependency2]);
return (
<div>
{/* 使用缓存的值 */}
<p>{memoizedValue}</p>
</div>
);
};
上の例では、 useMemo フックを使用して計算またはロジックをキャッシュします。最初のパラメータとして指定された関数は、最初のレンダリング時に 1 回呼び出され、依存する配列の値が変更された場合にのみ再計算されます。関数の戻り値はキャッシュされ、後続のレンダリングで再利用されます。これにより、高価な計算操作を不必要に繰り返すことがなくなり、パフォーマンスが向上します。
React の useMemo フックは、計算結果をキャッシュして不必要な計算やレンダリングを回避することで、コンポーネントの再レンダリングを減らすのに役立ちます。コンポーネントのレンダリング プロセスに負荷の高い計算が含まれる場合、または外部から渡される props の変更がレンダリング結果に影響しない場合は、useMemo を使用してパフォーマンスを向上させることができます。以下は useMemo を使用して再レンダリングを減らす方法を示す例です。
import React, { useMemo } from 'react';
const MyComponent = ({ data }) => {
// 使用useMemo缓存计算结果
const memoizedValue = useMemo(() => {
// 执行昂贵的计算操作
// 返回计算结果
}, [data]);
return (
<div>
{/* 使用缓存的值 */}
<p>{memoizedValue}</p>
</div>
);
};
上記の例では、data は MyComponent に外部から渡されるプロパティ (props) です。依存関係配列にデータを追加することで、データが変更された場合にのみ計算された関数を再実行するように React に指示します。それ以外の場合は、最後の計算が使用されるため、不必要な再レンダリングが回避されます。
浅い比較
useMemo フックは、デフォルトで参照の等価性チェックを使用します。つまり、浅い比較 (===) を使用して、前の値と現在の値を比較します。2 つのオブジェクトの場合、場合によっては、参照が変更されても、オブジェクトの値が変更されないことがあります。
const obj1 = { name: 'John', age: 30 };
const obj2 = { name: 'John', age: 30 };
obj1 === obj2 // false
この場合、再レンダリングを減らすために React useMemo の再計算を望まない場合があり、その場合は useMemo 依存関係の各項目をリストする必要があります。
const obj = { name: 'John', age: 30 };
const memoizedValue = useMemo(() => {
// 执行昂贵的计算操作
// 返回计算结果
}, [obj.name, obj.age]);
しかし、オブジェクトがより複雑になると、次のような状況のように、各オブジェクトのプロパティをリストするのが非常に面倒になり、配列オブジェクトを扱うのがさらに難しくなります。
console.log(fruits);
// => ['apple', 'banana', 'grapes']
const yellowFruits = useMemo(
() => fruits.filter((fruit) => fruit === "banana"),
[fruits]
);
// => ['banana']
フルーツの値が変更されると、 yellowFruits の参照が変更されますが、 yellowFruits 配列の値は変更されません。
console.log(fruits);
// => ['apple', 'banana', 'grapes', 'pineapple'] // ✅ new content, new reference
const yellowFruits = useMemo(
() => fruits.filter((fruit) => fruit === "banana"),
[fruits]
);
// => ['banana'] // ❌ same content, new reference
深い比較
因此,在一些使用场景下,只对比引用是无法判断值到底有没有变化,我们需要的是深比较。其中的一种解决方法是使用 JSON.stringify 将依赖项数组转换为字符串,并将其作为依赖项传递,以下是如何使用 useMemo 与字符串化依赖项的示例:
import React, { useMemo } from 'react';
const MyComponent = ({ data }) => {
const stringifiedData = JSON.stringify(data);
const memoizedValue = useMemo(() => {
// 执行昂贵的计算或逻辑
// 返回计算结果
}, [stringifiedData]);
return (
<div>
{/* 使用缓存的值 */}
<p>{memoizedValue}</p>
</div>
);
};
在上面的示例中,data 属性通过 JSON.stringify 转换为字符串,生成的字符串 stringifiedData 被用作 useMemo 的依赖项。这确保只有在 data 属性的字符串表示发生更改时才重新计算记忆化的值。然而,使用这种方法时要小心。如果字符串化的版本发生变化,即使实际数据没有变化,也可能会导致不必要的重新计算。重要的是要考虑具体的用例,并确定字符串化依赖项是否是你场景中最合适的方法。
另外一种解决方案是,使用自定义相等性检查来确定依赖项是否实际发生了变化,比如借助 lodash.isEqual 来判断对象的值是否发生变化。lodash.isEqual 是 lodash 库中的一个函数,用于深度比较两个值是否相等。它递归地比较两个值的每个属性和元素,以确定它们是否具有相同的值。以下是 lodash.isEqual 的示例:
const _ = require('lodash');
const obj1 = { name: 'John', age: 30 };
const obj2 = { name: 'John', age: 30 };
const obj3 = { name: 'John', age: 25 };
console.log(_.isEqual(obj1, obj2)); // true
console.log(_.isEqual(obj1, obj3)); // false
但是这些并不能直接来用,需要做一些改造: 使用 isEqual 比较的是组件状态更新前后的值,在函数组件中是无法直接拿到旧的 props 或 state 值,那么就需要借助 useRef 做一下缓存,
import React, { useMemo, useRef } from 'react';
import _ from 'lodash';
const MyComponent = ({ data }) => {
const ref = React.useRef(data);
const memoizedValue = useMemo(() => {
if (!_.isEqual(ref.current, data)) {
// 执行昂贵的计算或逻辑
ref.current = data;
}
// 返回计算结果
return ref.current;
}, [data]);
return (
<div>
{/* 使用缓存的值 */}
<p>{memoizedValue}</p>
</div>
);
};
这种用法的目的是在 data 发生变化时,通过引用对象 ref.current 缓存计算结果,避免重复计算。只有当 data 发生变化时,才会执行昂贵的计算逻辑。这可以提高性能,并确保在 data 未发生变化时仍使用之前计算的值。
对于自定义的检查函数,很多人提议 React 支持这样的 API,希望 useMemo 支持第三个参数,用于自定义对比函数:
const memoizedValue = useMemo(() => {
// 执行昂贵的计算操作
// 返回计算结果
- }, dependencies);
+ }, dependencies, isEqual);
如果我们去问 ChatGPT,ChaGPT 也会推荐我们这样去使用,但是当前 React 并不支持这样做,不要被 ChaGPT 误导了。
总结
useMemo 是一个非常有用的工具,可以帮助优化 React 组件的性能。通过在适当的地方使用 useMemo,可以避免不必要的计算,提高应用程序的响应性和效率。但是它只支持对于依赖的浅比较,如果需要深比较我们可以使用 JSON.stringify 或 isEqual 去实现。