| name | object-comparison |
| description | 使用浅相等和深相等检查高效比较对象和数组。对于记忆化、变化检测和条件渲染至关重要。 |
对象比较技能
何时使用此技能
当你需要以下操作时使用此技能:
- 优化渲染(React:shouldComponentUpdate、useMemo)
- 检测数据变化 而无需深入检查
- 在变化检测中比较状态之前/之后
- 基于参数变化记忆函数结果
- 在反应系统中防止不必要的更新
- 在测试中验证数据相等
快速入门
import { shallowEqual } from '@x-oasis/shallow-equal';
import { shallowArrayEqual } from '@x-oasis/shallow-array-equal';
import { isClamped } from '@x-oasis/is-clamped';
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { a: 1, b: { c: 2 } };
shallowEqual(obj1, obj2);
const arr1 = [1, 2, { x: 3 }];
const arr2 = [1, 2, { x: 3 }];
shallowArrayEqual(arr1, arr2);
isClamped(5, 0, 10);
isClamped(15, 0, 10);
可用工具
| 函数 | 目的 | 速度 | 深? |
|---|
shallowEqual | 比较对象 | ⚡⚡ | 否 |
shallowArrayEqual | 比较数组 | ⚡⚡ | 否 |
isClamped | 检查范围 | ⚡⚡⚡ | 不适用 |
layoutEqual | 比较布局 | ⚡ | 否 |
模式 1:浅相等
import { shallowEqual } from '@x-oasis/shallow-equal';
const user1 = { id: 1, name: 'John', tags: ['admin'] };
const user2 = { id: 1, name: 'John', tags: ['admin'] };
shallowEqual(user1, user2);
const tagsArray = ['admin'];
const user3 = { id: 1, name: 'John', tags: tagsArray };
const user4 = { id: 1, name: 'John', tags: tagsArray };
shallowEqual(user3, user4);
真实例子:React 优化
import { memo } from 'react';
import { shallowEqual } from '@x-oasis/shallow-equal';
const UserCard = memo(
({ user }) => <div>{user.name}</div>,
(prevProps, nextProps) => {
return shallowEqual(prevProps, nextProps);
}
);
模式 2:数组相等
import { shallowArrayEqual } from '@x-oasis/shallow-array-equal';
const arr1 = [1, 2, 3];
const arr2 = [1, 2, 3];
shallowArrayEqual(arr1, arr2);
const arr = [1, 2, 3];
const arr3 = arr;
const arr4 = arr;
shallowArrayEqual(arr3, arr4);
真实例子:React 中的依赖数组
import { useMemo } from 'react';
import { shallowArrayEqual } from '@x-oasis/shallow-array-equal';
function MyComponent({ items }) {
const memoizedItems = useMemo(() => {
return items.filter(item => item.active);
}, [items]);
}
模式 3:范围检查
import { isClamped, clamp } from '@x-oasis/is-clamped';
isClamped(50, 0, 100);
isClamped(150, 0, 100);
isClamped(-10, 0, 100);
clamp(150, 0, 100);
clamp(-10, 0, 100);
clamp(50, 0, 100);
真实例子:滑块/输入验证
import { isClamped, clamp } from '@x-oasis/is-clamped';
function handleSliderChange(value: number) {
const valid = isClamped(value, MIN_VALUE, MAX_VALUE);
if (!valid) {
value = clamp(value, MIN_VALUE, MAX_VALUE);
}
setValue(value);
}
模式 4:记忆化与相等
import { shallowEqual } from '@x-oasis/shallow-equal';
class MemoCache<T, R> {
private cache: { args: T; result: R } | null = null;
compute(args: T, fn: (args: T) => R): R {
if (this.cache && shallowEqual(this.cache.args, args)) {
return this.cache.result;
}
const result = fn(args);
this.cache = { args, result };
return result;
}
}
const cache = new MemoCache();
const selector = (state) => state.user.name;
const result1 = cache.compute(
{ user: { name: 'John' } },
selector
);
const result2 = cache.compute(
{ user: { name: 'John' } },
selector
);
模式 5:变化检测
import { shallowEqual } from '@x-oasis/shallow-equal';
class StateManager<T> {
private state: T;
private listeners: ((state: T) => void)[] = [];
constructor(initial: T) {
this.state = initial;
}
setState(newState: T) {
if (!shallowEqual(this.state, newState)) {
this.state = newState;
this.listeners.forEach(listener => listener(this.state));
}
}
subscribe(listener: (state: T) => void) {
this.listeners.push(listener);
return () => {
this.listeners = this.listeners.filter(l => l !== listener);
};
}
getState() {
return this.state;
}
}
const store = new StateManager({ count: 0 });
store.subscribe(state => {
console.log('状态改变:', state);
});
store.setState({ count: 0 });
store.setState({ count: 1 });
模式 6:条件渲染
import { shallowEqual } from '@x-oasis/shallow-equal';
function UserProfile({ previousUser, currentUser }) {
if (shallowEqual(previousUser, currentUser)) {
return <p>无改变</p>;
}
return (
<div>
<h2>用户已改变!</h2>
{currentUser.name !== previousUser.name && (
<p>名称:{previousUser.name} → {currentUser.name}</p>
)}
{currentUser.email !== previousUser.email && (
<p>邮箱:{previousUser.email} → {currentUser.email}</p>
)}
</div>
);
}
模式 7:深相等(手动)
import { shallowEqual } from '@x-oasis/shallow-equal';
function deepEqual<T>(a: T, b: T): boolean {
if (a === b) return true;
if (typeof a !== 'object' || typeof b !== 'object') {
return false;
}
if (a === null || b === null) {
return false;
}
if (!shallowEqual(a, b)) {
return false;
}
for (const key in a) {
if (!deepEqual(a[key], b[key])) {
return false;
}
}
return true;
}
deepEqual(
{ a: 1, b: { c: 2 } },
{ a: 1, b: { c: 2 } }
);
模式 8:不重新渲染时选择
import { selectValue } from '@x-oasis/select-value';
import { shallowEqual } from '@x-oasis/shallow-equal';
function useSelectorOptimized<T, S>(
state: T,
selector: (state: T) => S
) {
const [selected, setSelected] = useState<S | undefined>();
useEffect(() => {
const newSelected = selector(state);
if (!shallowEqual(selected, newSelected)) {
setSelected(newSelected);
}
}, [state, selector]);
return selected;
}
function UserName({ userId }) {
const user = useSelectorOptimized(
store.getState(),
state => ({ name: state.users[userId]?.name })
);
return <div>{user?.name}</div>;
}
最佳实践
✅ 做法
if (shallowEqual(prevData, newData)) {
return;
}
if (shallowEqual(props, prevProps)) {
return prevResult;
}
const value = clamp(userInput, MIN, MAX);
const getActiveUsers = (state) => {
return state.users.filter(u => u.active);
};
❌ 不做法
const equal = shallowEqual(
{ a: { b: { c: 1 } } },
{ a: { b: { c: 1 } } }
);
shallowEqual([1, 2], [1, 2]);
常见陷阱
陷阱 1:混淆浅 vs 深
const obj1 = { user: { id: 1 } };
const obj2 = { user: { id: 2 } };
shallowEqual(obj1, obj2);
if (obj1.user.id !== obj2.user.id) {
}
陷阱 2:数组 vs 对象比较
const result = shallowEqual(
[1, 2, 3],
{ 0: 1, 1: 2, 2: 3 }
);
const arrayResult = shallowArrayEqual([1, 2], [1, 2]);
const objectResult = shallowEqual({ a: 1 }, { a: 1 });
陷阱 3:记忆化失效
function MyComponent(props) {
const config = { timeout: 1000 };
return (
<Child
config={config}
onChange={handleChange}
/>
);
}
const DEFAULT_CONFIG = { timeout: 1000 };
function MyComponent(props) {
return (
<Child
config={DEFAULT_CONFIG}
onChange={handleChange}
/>
);
}
参考资料