Skip to content

Instantly share code, notes, and snippets.

@jerryc05
Last active February 19, 2026 08:39
Show Gist options
  • Select an option

  • Save jerryc05/b3d0c0ad2f3c92a2d3fe1610d1483376 to your computer and use it in GitHub Desktop.

Select an option

Save jerryc05/b3d0c0ad2f3c92a2d3fe1610d1483376 to your computer and use it in GitHub Desktop.
import { structuralUpdate } from './structural-update.ts'
class C {
setState(
state: ((prevState: Readonly<IState>, props: Readonly<IProps>) => Partial<IState>) | Partial<IState>,
cb?: () => void,
) {
super.setState((prevState, props) => {
const newState = typeof state === 'function' ? state(prevState, props) : state;
// 稳定字段,减少子组件重渲染
Object.entries(newState).forEach(
([key, value]) =>
(newState[key as keyof typeof newState] = structuralUpdate(prevState[key as keyof IState], value)),
);
return newState as IState;
}, cb);
}
}
export function isJsonObjDeepEqual(obj1, obj2) {
try {
/* eslint-disable curly */
// 如果引用相同,直接返回 true
if (Object.is(obj1, obj2)) return true;
// 如果不是对象或者是 null,由于经过了上面的引用检查,这里一定不相等
if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) return false;
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (const key of keys1) {
// 使用 hasOwnProperty 替代 includes 提高查找速度
if (!Object.prototype.hasOwnProperty.call(obj2, key)) return false;
// 递归
if (!isJsonObjDeepEqual(obj1[key], obj2[key])) return false;
}
return true;
/* eslint-enable curly */
} catch (e) {
// 如果发生 RangeError (栈溢出),说明存在循环引用或嵌套过深,此时无法断定相等,安全起见返回 false
return false;
}
}
/** 结构化更新:将 o1 更新为 o2,但尽可能保留 o1 中未改变部分的引用 */
export function structuralUpdate<T>(oldData: T, newData: T): T {
// 1. 如果引用完全相同,直接返回
if (oldData === newData || Object.is(oldData, newData)) {
return oldData;
}
// 2. 数组
if (Array.isArray(oldData) && Array.isArray(newData)) {
if (oldData.length !== newData.length) {
return newData;
}
let hasChanged = false as boolean;
const nextArray = newData.map((item, index) => {
const updated = structuralUpdate(oldData[index], item);
if (updated !== oldData[index] && !Object.is(updated, oldData[index])) {
hasChanged = true;
}
return updated;
});
return hasChanged ? (nextArray as unknown as T) : oldData;
}
// 3. 纯对象
if (
oldData &&
newData &&
typeof oldData === 'object' &&
typeof newData === 'object' &&
Object.prototype.toString.call(oldData) === '[object Object]' &&
Object.prototype.toString.call(newData) === '[object Object]'
) {
const oldKeys = Object.keys(oldData);
const newKeys = Object.keys(newData);
if (oldKeys.length !== newKeys.length) {
return newData;
}
let hasChanged = false;
const nextObject = {};
for (const key of newKeys) {
const updated = structuralUpdate(oldData[key], newData[key]);
nextObject[key] = updated;
if (updated !== oldData[key] && !Object.is(updated, oldData[key])) {
hasChanged = true;
}
}
return hasChanged ? (nextObject as T) : oldData;
}
// 4. 基本类型或类型改变,直接返回新值
return newData;
}
/** 保证函数引用永远不变,同时避免闭包陷阱 */
export function useEvent<T extends (...args: any[]) => any>(fn: T): T {
const ref = useRef<T>(fn);
useLayoutEffect(() => {
ref.current = fn;
});
const memoizedFn = useCallback(
((...args: Parameters<T>): ReturnType<T> => ref.current(...args)) as T,
[],
);
return memoizedFn;
}
import { isJsonObjDeepEqual } from './is-json-obj-deep-equal.ts'
/** 对比组件更新前后的 Props 和 State,定位并控制台提醒由于引用不一致导致的非必要重绘 */
export function whyDidYouRender(name: string, prevProps, nextProps, prevState, nextState) {
const changes = {};
const compare = (prev, next, type: string) => {
const allKeys = new Set([...Object.keys(prev || {}), ...Object.keys(next || {})]);
for (const key of allKeys) {
const p = prev?.[key];
const n = next?.[key];
if (!Object.is(p, n)) {
let status = '✅ 确实变了';
// 只有当两者都是对象时,才执行耗时的深比较
if (typeof p === 'object' && typeof n === 'object' && p !== null && n !== null && isJsonObjDeepEqual(p, n))
status = '❗内容一致,引用变了';
changes[`${type}: ${key}`] = { old: p, new: n, _: status };
}
}
};
compare(prevProps, nextProps, 'P');
compare(prevState, nextState, 'S');
if (Object.keys(changes).length > 0) {
console.warn(`[WDYR] ${name} 变更:`, changes);
} else { // 处理 forceUpdate 或父组件渲染导致的无效重绘
console.warn(`[WDYR] ${name} - 无意义渲染 (Props/State均无变化)`);
}
}
// 确保你在组件内也定义了相应的类型
componentDidUpdate(prevProps: unknown, prevState: unknown) {
whyDidYouRender(
this.constructor.name,
prevProps,
this.props,
prevState,
this.state
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment