Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | 6x 6x 6x 1x 5x 6x 2x 3x 2x 1x 1x 5x 5x 8x 30x 2x 5x 6x 2x 3x 5x 5x 5x 5x 3x 5x | /**
* Performance Optimization Utilities
*
* Collection of utilities for optimizing React rendering performance:
* - Memoization helpers for expensive calculations
* - Shallow comparison utilities for React.memo
* - GPU acceleration style helpers
* - Performance measurement utilities
*
* @module utils/performanceOptimization
* @category Performance
* @korean 성능 최적화 유틸리티
*/
import React from 'react';
/**
* Shallow comparison for props
* Used with React.memo for performance optimization
*
* @param prevProps - Previous props
* @param nextProps - Next props
* @returns true if props are equal (skip re-render), false otherwise
*/
export function shallowCompare<T extends Record<string, unknown>>(
prevProps: T,
nextProps: T
): boolean {
const prevKeys = Object.keys(prevProps);
const nextKeys = Object.keys(nextProps);
if (prevKeys.length !== nextKeys.length) {
return false;
}
for (const key of prevKeys) {
if (prevProps[key] !== nextProps[key]) {
return false;
}
}
return true;
}
/**
* Create a memoized component with custom comparison
*
* @param component - Component to memoize
* @param compareKeys - Top-level prop keys to compare for equality
* @returns Memoized component
*
* @example
* ```tsx
* // If PlayerHUD receives props like { playerHealth, playerStamina }
* const MemoizedHUD = memoizeComponent(PlayerHUD, ['playerHealth', 'playerStamina']);
* ```
*/
export function memoizeComponent<P extends Record<string, unknown>>(
component: React.FC<P>,
compareKeys?: (keyof P)[]
): React.NamedExoticComponent<P> {
if (!compareKeys || compareKeys.length === 0) {
return React.memo(component);
}
return React.memo(component, (prevProps, nextProps) => {
for (const key of compareKeys) {
if (prevProps[key] !== nextProps[key]) {
return false; // Props changed, re-render
}
}
return true; // Props same, skip re-render
});
}
/**
* GPU acceleration styles
* Forces GPU layer creation for smooth animations
*/
export const GPU_ACCELERATION_STYLES = {
/**
* Force GPU layer with translateZ
*/
transform: 'translateZ(0)',
/**
* Enable hardware acceleration
*/
backfaceVisibility: 'hidden' as const,
/**
* Hint browser about upcoming changes
*/
willChange: 'transform, opacity' as const,
} as const;
/**
* Supported CSS properties for GPU-accelerated transitions
* 제한된 전환 대상 속성 (transform, opacity)
*/
export type GPUTransitionProperty = 'transform' | 'opacity';
/**
* Create a valid GPU-accelerated CSS transition string
*
* Ensures correct syntax such as:
* "transform 0.2s ease, opacity 0.2s ease"
* instead of invalid:
* "transform, opacity 0.2s ease"
*
* @param properties - CSS properties to animate (default: ['transform', 'opacity'])
* @param duration - Transition duration (default: '0.2s')
* @param timingFunction - Timing function (default: 'ease')
* @returns Valid CSS transition shorthand string
*/
export function createGPUAcceleratedTransition(
properties: readonly GPUTransitionProperty[] = ['transform', 'opacity'],
duration: string = '0.2s',
timingFunction: string = 'ease'
): string {
return properties
.map((property) => `${property} ${duration} ${timingFunction}`)
.join(', ');
}
/**
* Apply GPU acceleration to CSS-in-JS style object
*
* @param styles - Base styles
* @returns Styles with GPU acceleration
*/
export function withGPUAcceleration<T extends React.CSSProperties>(
styles: T
): T & typeof GPU_ACCELERATION_STYLES {
return {
...styles,
...GPU_ACCELERATION_STYLES,
};
}
/**
* Performance-optimized animation styles
* Uses only transform and opacity for 60fps animations
*
* @param translateX - X translation in px
* @param translateY - Y translation in px
* @param opacity - Opacity (0-1)
* @param scale - Scale factor (default: 1)
* @returns Optimized style object
*/
export function optimizedAnimationStyle(
translateX = 0,
translateY = 0,
opacity = 1,
scale = 1
): React.CSSProperties {
return {
transform: `translate3d(${translateX}px, ${translateY}px, 0) scale(${scale})`,
opacity,
willChange: 'transform, opacity',
backfaceVisibility: 'hidden',
};
}
/**
* Measure component render time
* Only runs in development mode
*
* @param componentName - Name of component being measured
* @param callback - Function to measure
* @returns Result of callback
*/
export function measureRender<T>(
componentName: string,
callback: () => T
): T {
if (import.meta.env.DEV) {
const start = performance.now();
const result = callback();
const end = performance.now();
const duration = end - start;
if (duration > 16.67) { // Slower than 60fps frame budget
console.warn(
`[Performance] ${componentName} render took ${duration.toFixed(2)}ms (>16.67ms budget)`
);
}
return result;
}
return callback();
}
/**
* Create a render counter for debugging
* Tracks how many times a component renders
*
* Note: This is intentionally commented out due to React compiler
* limitations with ref mutations during render. For production use,
* consider using React DevTools Profiler instead.
*
* @param componentName - Name of component
* @returns Render count (always returns 0)
*/
export function useRenderCount(componentName: string): number {
// Disabled due to ESLint react-compiler rules
// See: https://react.dev/reference/react/useRef#caveats
if (import.meta.env.DEV) {
console.log(`[Render] ${componentName} is rendering (counter disabled)`);
}
return 0;
/* Original implementation disabled due to lint errors
const renderCountRef = React.useRef(0);
renderCountRef.current += 1;
React.useEffect(() => {
if (import.meta.env.DEV) {
console.log(`[Render] ${componentName} rendered ${renderCountRef.current} times`);
}
}, [componentName]);
return renderCountRef.current;
*/
}
/**
* Batch state updates to reduce re-renders
*
* @param updates - Array of state update functions
*/
export function batchUpdates(updates: (() => void)[]): void {
React.startTransition(() => {
updates.forEach(update => update());
});
}
/**
* Check if selected object properties have changed using shallow/reference equality
*
* This compares only the top-level values at the specified keys using strict
* equality (`===`). If a key refers to an object or array, only the reference
* is compared, not its contents. This is suitable for React.memo-style
* performance optimizations where prop references are kept stable.
*
* @param prev - Previous object
* @param next - Next object
* @param keys - Keys to check with shallow/reference equality
* @returns true if any specified key value changed by reference
*/
export function hasPropsChanged<T extends Record<string, unknown>>(
prev: T,
next: T,
keys: (keyof T)[]
): boolean {
for (const key of keys) {
if (prev[key] !== next[key]) {
return true;
}
}
return false;
}
/**
* Create a stable callback reference that doesn't change between renders
* Similar to useCallback but with automatic dependency detection
*
* @param callback - Callback function
* @returns Stable callback reference
*/
export function useStableCallback<T extends (...args: never[]) => unknown>(
callback: T
): T {
const callbackRef = React.useRef(callback);
// Update ref on each render
React.useLayoutEffect(() => {
callbackRef.current = callback;
});
// Return stable function that calls latest callback
const stableCallback = React.useCallback(
(...args: Parameters<T>) => callbackRef.current(...args),
[]
) as T;
return stableCallback;
}
|