feat(performance): add Redux performance monitoring and memoized selectors

- Introduced a Redux performance monitoring system to log action metrics, including duration and state size.
- Implemented middleware for tracking performance of Redux actions and logging slow actions in development.
- Added utility functions for analyzing performance metrics and generating recommendations for optimization.
- Created memoized selectors to enhance performance and prevent unnecessary re-renders across various application states.
This commit is contained in:
chamiakJ
2025-06-21 18:40:57 +05:30
parent b617d15c62
commit 5a475a84b5
2 changed files with 203 additions and 0 deletions

View File

@@ -0,0 +1,122 @@
import { Middleware } from '@reduxjs/toolkit';
// Performance monitoring for Redux store
export interface PerformanceMetrics {
actionType: string;
duration: number;
timestamp: number;
stateSize: number;
}
class ReduxPerformanceMonitor {
private metrics: PerformanceMetrics[] = [];
private maxMetrics = 100; // Keep last 100 metrics
private slowActionThreshold = 50; // Log actions taking more than 50ms
logMetric(metric: PerformanceMetrics) {
this.metrics.push(metric);
// Keep only recent metrics
if (this.metrics.length > this.maxMetrics) {
this.metrics = this.metrics.slice(-this.maxMetrics);
}
// Log slow actions in development
if (process.env.NODE_ENV === 'development' && metric.duration > this.slowActionThreshold) {
console.warn(`Slow Redux action detected: ${metric.actionType} took ${metric.duration}ms`);
}
}
getMetrics() {
return [...this.metrics];
}
getSlowActions(threshold = this.slowActionThreshold) {
return this.metrics.filter(m => m.duration > threshold);
}
getAverageActionTime() {
if (this.metrics.length === 0) return 0;
const total = this.metrics.reduce((sum, m) => sum + m.duration, 0);
return total / this.metrics.length;
}
reset() {
this.metrics = [];
}
}
export const performanceMonitor = new ReduxPerformanceMonitor();
// Redux middleware for performance monitoring
export const performanceMiddleware: Middleware = (store) => (next) => (action: any) => {
const start = performance.now();
const result = next(action);
const end = performance.now();
const duration = end - start;
// Calculate approximate state size (in development only)
let stateSize = 0;
if (process.env.NODE_ENV === 'development') {
try {
stateSize = JSON.stringify(store.getState()).length;
} catch (e) {
stateSize = -1; // Indicates serialization error
}
}
performanceMonitor.logMetric({
actionType: action.type || 'unknown',
duration,
timestamp: Date.now(),
stateSize,
});
return result;
};
// Hook to access performance metrics in components
export function useReduxPerformance() {
return {
metrics: performanceMonitor.getMetrics(),
slowActions: performanceMonitor.getSlowActions(),
averageTime: performanceMonitor.getAverageActionTime(),
reset: () => performanceMonitor.reset(),
};
}
// Utility to detect potential performance issues
export function analyzeReduxPerformance() {
const metrics = performanceMonitor.getMetrics();
const analysis = {
totalActions: metrics.length,
slowActions: performanceMonitor.getSlowActions().length,
averageActionTime: performanceMonitor.getAverageActionTime(),
largestStateSize: Math.max(...metrics.map(m => m.stateSize)),
mostFrequentActions: {} as Record<string, number>,
recommendations: [] as string[],
};
// Count action frequencies
metrics.forEach(m => {
analysis.mostFrequentActions[m.actionType] =
(analysis.mostFrequentActions[m.actionType] || 0) + 1;
});
// Generate recommendations
if (analysis.slowActions > analysis.totalActions * 0.1) {
analysis.recommendations.push('Consider optimizing selectors with createSelector');
}
if (analysis.largestStateSize > 1000000) { // 1MB
analysis.recommendations.push('State size is large - consider normalizing data');
}
if (analysis.averageActionTime > 20) {
analysis.recommendations.push('Average action time is high - check for expensive reducers');
}
return analysis;
}

View File

@@ -0,0 +1,81 @@
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from './store';
// Memoized selectors for better performance
// These prevent unnecessary re-renders when state hasn't actually changed
// Auth selectors
export const selectAuth = (state: RootState) => state.auth;
export const selectUser = (state: RootState) => state.userReducer;
export const selectIsAuthenticated = createSelector(
[selectAuth],
(auth) => !!auth.user
);
// Project selectors
export const selectProjects = (state: RootState) => state.projectsReducer;
export const selectCurrentProject = (state: RootState) => state.projectReducer;
export const selectProjectMembers = (state: RootState) => state.projectMemberReducer;
// Task selectors
export const selectTasks = (state: RootState) => state.taskReducer;
export const selectTaskManagement = (state: RootState) => state.taskManagement;
export const selectTaskSelection = (state: RootState) => state.taskManagementSelection;
// UI State selectors
export const selectTheme = (state: RootState) => state.themeReducer;
export const selectLocale = (state: RootState) => state.localesReducer;
export const selectAlerts = (state: RootState) => state.alertsReducer;
// Board and Project View selectors
export const selectBoard = (state: RootState) => state.boardReducer;
export const selectProjectView = (state: RootState) => state.projectViewReducer;
export const selectProjectDrawer = (state: RootState) => state.projectDrawerReducer;
// Task attributes selectors
export const selectTaskPriorities = (state: RootState) => state.priorityReducer;
export const selectTaskLabels = (state: RootState) => state.taskLabelsReducer;
export const selectTaskStatuses = (state: RootState) => state.taskStatusReducer;
export const selectTaskDrawer = (state: RootState) => state.taskDrawerReducer;
// Settings selectors
export const selectMembers = (state: RootState) => state.memberReducer;
export const selectClients = (state: RootState) => state.clientReducer;
export const selectJobs = (state: RootState) => state.jobReducer;
export const selectTeams = (state: RootState) => state.teamReducer;
export const selectCategories = (state: RootState) => state.categoriesReducer;
export const selectLabels = (state: RootState) => state.labelReducer;
// Reporting selectors
export const selectReporting = (state: RootState) => state.reportingReducer;
export const selectProjectReports = (state: RootState) => state.projectReportsReducer;
export const selectMemberReports = (state: RootState) => state.membersReportsReducer;
export const selectTimeReports = (state: RootState) => state.timeReportsOverviewReducer;
// Admin and billing selectors
export const selectAdminCenter = (state: RootState) => state.adminCenterReducer;
export const selectBilling = (state: RootState) => state.billingReducer;
// Schedule and date selectors
export const selectSchedule = (state: RootState) => state.scheduleReducer;
export const selectDate = (state: RootState) => state.dateReducer;
// Feature-specific selectors
export const selectHomePage = (state: RootState) => state.homePageReducer;
export const selectAccountSetup = (state: RootState) => state.accountSetupReducer;
export const selectRoadmap = (state: RootState) => state.roadmapReducer;
export const selectGroupByFilter = (state: RootState) => state.groupByFilterDropdownReducer;
// Memoized computed selectors for common use cases
export const selectHasActiveProject = createSelector(
[selectCurrentProject],
(project) => !!project && Object.keys(project).length > 0
);
export const selectIsLoading = createSelector(
[selectTasks, selectProjects],
(tasks, projects) => {
// Check if any major feature is loading
return (tasks as any)?.loading || (projects as any)?.loading;
}
);