Last active
February 19, 2026 08:39
-
-
Save jerryc05/b3d0c0ad2f3c92a2d3fe1610d1483376 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** 结构化更新:将 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; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** 保证函数引用永远不变,同时避免闭包陷阱 */ | |
| 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; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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