feat(performance): enhance application performance with optimizations and monitoring
- Updated package dependencies for improved localization support and performance. - Introduced CSS performance optimizations to prevent layout shifts and enhance rendering efficiency. - Implemented asset preloading and lazy loading strategies for critical components to improve load times. - Enhanced translation loading with optimized caching and background loading strategies. - Added performance monitoring utilities to track key metrics and improve user experience. - Refactored task management components to utilize new performance features and ensure efficient rendering. - Introduced new utility functions for asset and CSS optimizations to streamline resource management.
This commit is contained in:
319
worklenz-frontend/src/utils/redux-optimizations.ts
Normal file
319
worklenz-frontend/src/utils/redux-optimizations.ts
Normal file
@@ -0,0 +1,319 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { shallowEqual } from 'react-redux';
|
||||
import { RootState } from '@/app/store';
|
||||
import { Task } from '@/types/task-management.types';
|
||||
|
||||
// Performance-optimized selectors using createSelector for memoization
|
||||
|
||||
// Basic state selectors (these will be cached)
|
||||
const selectTaskManagementState = (state: RootState) => state.taskManagement;
|
||||
const selectTaskReducerState = (state: RootState) => state.taskReducer;
|
||||
const selectThemeState = (state: RootState) => state.themeReducer;
|
||||
const selectTeamMembersState = (state: RootState) => state.teamMembersReducer;
|
||||
const selectTaskStatusState = (state: RootState) => state.taskStatusReducer;
|
||||
const selectPriorityState = (state: RootState) => state.priorityReducer;
|
||||
const selectPhaseState = (state: RootState) => state.phaseReducer;
|
||||
const selectTaskLabelsState = (state: RootState) => state.taskLabelsReducer;
|
||||
|
||||
// Memoized task selectors
|
||||
export const selectOptimizedAllTasks = createSelector(
|
||||
[selectTaskManagementState],
|
||||
(taskManagementState) => Object.values(taskManagementState.entities || {})
|
||||
);
|
||||
|
||||
export const selectOptimizedTasksById = createSelector(
|
||||
[selectTaskManagementState],
|
||||
(taskManagementState) => taskManagementState.entities || {}
|
||||
);
|
||||
|
||||
export const selectOptimizedTaskGroups = createSelector(
|
||||
[selectTaskManagementState],
|
||||
(taskManagementState) => taskManagementState.groups || []
|
||||
);
|
||||
|
||||
export const selectOptimizedCurrentGrouping = createSelector(
|
||||
[selectTaskManagementState],
|
||||
(taskManagementState) => taskManagementState.grouping || 'status'
|
||||
);
|
||||
|
||||
export const selectOptimizedLoading = createSelector(
|
||||
[selectTaskManagementState],
|
||||
(taskManagementState) => taskManagementState.loading || false
|
||||
);
|
||||
|
||||
export const selectOptimizedError = createSelector(
|
||||
[selectTaskManagementState],
|
||||
(taskManagementState) => taskManagementState.error
|
||||
);
|
||||
|
||||
export const selectOptimizedSearch = createSelector(
|
||||
[selectTaskManagementState],
|
||||
(taskManagementState) => taskManagementState.search || ''
|
||||
);
|
||||
|
||||
export const selectOptimizedArchived = createSelector(
|
||||
[selectTaskManagementState],
|
||||
(taskManagementState) => taskManagementState.archived || false
|
||||
);
|
||||
|
||||
// Theme selectors
|
||||
export const selectOptimizedIsDarkMode = createSelector(
|
||||
[selectThemeState],
|
||||
(themeState) => themeState?.mode === 'dark'
|
||||
);
|
||||
|
||||
export const selectOptimizedThemeMode = createSelector(
|
||||
[selectThemeState],
|
||||
(themeState) => themeState?.mode || 'light'
|
||||
);
|
||||
|
||||
// Team members selectors
|
||||
export const selectOptimizedTeamMembers = createSelector(
|
||||
[selectTeamMembersState],
|
||||
(teamMembersState) => teamMembersState.teamMembers || []
|
||||
);
|
||||
|
||||
export const selectOptimizedTeamMembersById = createSelector(
|
||||
[selectOptimizedTeamMembers],
|
||||
(teamMembers) => {
|
||||
if (!Array.isArray(teamMembers)) return {};
|
||||
const membersById: Record<string, any> = {};
|
||||
teamMembers.forEach((member: any) => {
|
||||
membersById[member.id] = member;
|
||||
});
|
||||
return membersById;
|
||||
}
|
||||
);
|
||||
|
||||
// Task status selectors
|
||||
export const selectOptimizedTaskStatuses = createSelector(
|
||||
[selectTaskStatusState],
|
||||
(taskStatusState) => taskStatusState.status || []
|
||||
);
|
||||
|
||||
export const selectOptimizedTaskStatusCategories = createSelector(
|
||||
[selectTaskStatusState],
|
||||
(taskStatusState) => taskStatusState.statusCategories || []
|
||||
);
|
||||
|
||||
// Priority selectors
|
||||
export const selectOptimizedPriorities = createSelector(
|
||||
[selectPriorityState],
|
||||
(priorityState) => priorityState.priorities || []
|
||||
);
|
||||
|
||||
// Phase selectors
|
||||
export const selectOptimizedPhases = createSelector(
|
||||
[selectPhaseState],
|
||||
(phaseState) => phaseState.phaseList || []
|
||||
);
|
||||
|
||||
// Labels selectors
|
||||
export const selectOptimizedLabels = createSelector(
|
||||
[selectTaskLabelsState],
|
||||
(labelsState) => labelsState.labels || []
|
||||
);
|
||||
|
||||
// Complex computed selectors
|
||||
export const selectOptimizedTasksByGroup = createSelector(
|
||||
[selectOptimizedAllTasks, selectOptimizedTaskGroups],
|
||||
(tasks, groups) => {
|
||||
const tasksByGroup: Record<string, Task[]> = {};
|
||||
|
||||
groups.forEach((group: any) => {
|
||||
tasksByGroup[group.id] = group.tasks || [];
|
||||
});
|
||||
|
||||
return tasksByGroup;
|
||||
}
|
||||
);
|
||||
|
||||
export const selectOptimizedTaskCounts = createSelector(
|
||||
[selectOptimizedTasksByGroup],
|
||||
(tasksByGroup) => {
|
||||
const counts: Record<string, number> = {};
|
||||
Object.keys(tasksByGroup).forEach(groupId => {
|
||||
counts[groupId] = tasksByGroup[groupId].length;
|
||||
});
|
||||
return counts;
|
||||
}
|
||||
);
|
||||
|
||||
export const selectOptimizedTotalTaskCount = createSelector(
|
||||
[selectOptimizedAllTasks],
|
||||
(tasks) => tasks.length
|
||||
);
|
||||
|
||||
// Selection state selectors
|
||||
export const selectOptimizedSelectedTaskIds = createSelector(
|
||||
[(state: RootState) => state.taskManagementSelection?.selectedTaskIds],
|
||||
(selectedTaskIds) => selectedTaskIds || []
|
||||
);
|
||||
|
||||
export const selectOptimizedSelectedTasksCount = createSelector(
|
||||
[selectOptimizedSelectedTaskIds],
|
||||
(selectedTaskIds) => selectedTaskIds.length
|
||||
);
|
||||
|
||||
export const selectOptimizedSelectedTasks = createSelector(
|
||||
[selectOptimizedAllTasks, selectOptimizedSelectedTaskIds],
|
||||
(tasks, selectedTaskIds) => {
|
||||
return tasks.filter((task: Task) => selectedTaskIds.includes(task.id));
|
||||
}
|
||||
);
|
||||
|
||||
// Performance utilities
|
||||
export const createShallowEqualSelector = <T>(
|
||||
selector: (state: RootState) => T
|
||||
) => {
|
||||
let lastResult: T;
|
||||
let lastArgs: any;
|
||||
|
||||
return (state: RootState): T => {
|
||||
const newArgs = selector(state);
|
||||
|
||||
if (!shallowEqual(newArgs, lastArgs)) {
|
||||
lastArgs = newArgs;
|
||||
lastResult = newArgs;
|
||||
}
|
||||
|
||||
return lastResult;
|
||||
};
|
||||
};
|
||||
|
||||
// Memoized equality functions for React.memo
|
||||
export const taskPropsAreEqual = (
|
||||
prevProps: any,
|
||||
nextProps: any
|
||||
): boolean => {
|
||||
// Quick reference checks first
|
||||
if (prevProps.task === nextProps.task) return true;
|
||||
if (!prevProps.task || !nextProps.task) return false;
|
||||
if (prevProps.task.id !== nextProps.task.id) return false;
|
||||
|
||||
// Check other props
|
||||
if (prevProps.isSelected !== nextProps.isSelected) return false;
|
||||
if (prevProps.isDragOverlay !== nextProps.isDragOverlay) return false;
|
||||
if (prevProps.groupId !== nextProps.groupId) return false;
|
||||
if (prevProps.currentGrouping !== nextProps.currentGrouping) return false;
|
||||
if (prevProps.level !== nextProps.level) return false;
|
||||
|
||||
// Deep comparison for task properties that commonly change
|
||||
const taskProps = [
|
||||
'title',
|
||||
'progress',
|
||||
'status',
|
||||
'priority',
|
||||
'description',
|
||||
'startDate',
|
||||
'dueDate',
|
||||
'updatedAt',
|
||||
'sub_tasks_count',
|
||||
'show_sub_tasks'
|
||||
];
|
||||
|
||||
for (const prop of taskProps) {
|
||||
if (prevProps.task[prop] !== nextProps.task[prop]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Compare arrays with shallow equality
|
||||
if (!shallowEqual(prevProps.task.assignees, nextProps.task.assignees)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!shallowEqual(prevProps.task.labels, nextProps.task.labels)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const taskGroupPropsAreEqual = (
|
||||
prevProps: any,
|
||||
nextProps: any
|
||||
): boolean => {
|
||||
// Quick reference checks
|
||||
if (prevProps.group === nextProps.group) return true;
|
||||
if (!prevProps.group || !nextProps.group) return false;
|
||||
if (prevProps.group.id !== nextProps.group.id) return false;
|
||||
|
||||
// Check task lists
|
||||
if (!shallowEqual(prevProps.group.taskIds, nextProps.group.taskIds)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check other props
|
||||
if (prevProps.projectId !== nextProps.projectId) return false;
|
||||
if (prevProps.currentGrouping !== nextProps.currentGrouping) return false;
|
||||
if (!shallowEqual(prevProps.selectedTaskIds, nextProps.selectedTaskIds)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// Performance monitoring utilities
|
||||
export const createPerformanceSelector = <T>(
|
||||
selector: (state: RootState) => T,
|
||||
name: string
|
||||
) => {
|
||||
return createSelector(
|
||||
[selector],
|
||||
(result) => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const startTime = performance.now();
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
if (duration > 5) {
|
||||
console.warn(`Slow selector ${name}: ${duration.toFixed(2)}ms`);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Utility to create batched state updates
|
||||
export const createBatchedStateUpdate = <T>(
|
||||
updateFn: (updates: T[]) => void,
|
||||
delay: number = 16 // One frame
|
||||
) => {
|
||||
let pending: T[] = [];
|
||||
let timeoutId: NodeJS.Timeout | null = null;
|
||||
|
||||
return (update: T) => {
|
||||
pending.push(update);
|
||||
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
timeoutId = setTimeout(() => {
|
||||
const updates = [...pending];
|
||||
pending = [];
|
||||
timeoutId = null;
|
||||
updateFn(updates);
|
||||
}, delay);
|
||||
};
|
||||
};
|
||||
|
||||
// Performance monitoring hook
|
||||
export const useReduxPerformanceMonitor = () => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const startTime = performance.now();
|
||||
|
||||
return () => {
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
if (duration > 16) {
|
||||
console.warn(`Slow Redux operation: ${duration.toFixed(2)}ms`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return () => {}; // No-op in production
|
||||
};
|
||||
Reference in New Issue
Block a user