perf(store): 优化 selector 性能并移除未使用代码
- 使用 createSelector 和 useMemo 优化 medications 和 tabBarConfig 的 selector,避免不必要的重渲染 - 添加空数组常量 EMPTY_RECORDS_ARRAY,减少对象创建开销 - 移除 _layout.tsx 中未使用的路由配置 - 删除过时的通知实现文档 - 移除 pushNotificationManager 中未使用的 token 刷新监听器 - 禁用开发环境的后台任务调试工具初始化
This commit is contained in:
@@ -11,7 +11,7 @@ import type {
|
||||
MedicationStatus,
|
||||
} from '@/types/medication';
|
||||
import { convertMedicationDataToWidget, syncMedicationDataToWidget } from '@/utils/widgetDataSync';
|
||||
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createAsyncThunk, createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import dayjs from 'dayjs';
|
||||
import type { RootState } from './index';
|
||||
|
||||
@@ -695,6 +695,9 @@ export const {
|
||||
|
||||
// ==================== Selectors ====================
|
||||
|
||||
// 空数组常量,避免每次都创建新数组
|
||||
const EMPTY_RECORDS_ARRAY: MedicationRecord[] = [];
|
||||
|
||||
export const selectMedicationsState = (state: RootState) => state.medications;
|
||||
export const selectMedications = (state: RootState) => state.medications.medications;
|
||||
export const selectActiveMedications = (state: RootState) =>
|
||||
@@ -708,7 +711,7 @@ export const selectOverallStats = (state: RootState) => state.medications.overal
|
||||
* 获取指定日期的服药记录
|
||||
*/
|
||||
export const selectMedicationRecordsByDate = (date: string) => (state: RootState) => {
|
||||
return state.medications.medicationRecords[date] || [];
|
||||
return state.medications.medicationRecords[date] || EMPTY_RECORDS_ARRAY;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -716,7 +719,7 @@ export const selectMedicationRecordsByDate = (date: string) => (state: RootState
|
||||
*/
|
||||
export const selectSelectedDateMedicationRecords = (state: RootState) => {
|
||||
const selectedDate = state.medications.selectedDate;
|
||||
return state.medications.medicationRecords[selectedDate] || [];
|
||||
return state.medications.medicationRecords[selectedDate] || EMPTY_RECORDS_ARRAY;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -735,72 +738,76 @@ export const selectSelectedDateStats = (state: RootState) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取指定日期的展示项列表(用于UI渲染)
|
||||
* 获取指定日期的展示项列表(用于UI渲染)- 使用 createSelector 进行 memoization
|
||||
* 将药物记录和药物信息合并为展示项
|
||||
* 排序规则:优先显示未服用的药品(upcoming、missed),然后是已服用的药品(taken、skipped)
|
||||
*/
|
||||
export const selectMedicationDisplayItemsByDate = (date: string) => (state: RootState) => {
|
||||
const records = state.medications.medicationRecords[date] || [];
|
||||
const medications = state.medications.medications;
|
||||
|
||||
// 创建药物ID到药物的映射
|
||||
const medicationMap = new Map<string, Medication>();
|
||||
medications.forEach((med) => medicationMap.set(med.id, med));
|
||||
|
||||
// 转换为展示项
|
||||
const displayItems = records
|
||||
.map((record) => {
|
||||
const medication = medicationMap.get(record.medicationId);
|
||||
if (!medication) return null;
|
||||
export const selectMedicationDisplayItemsByDate = (date: string) =>
|
||||
createSelector(
|
||||
[
|
||||
(state: RootState) => state.medications.medicationRecords[date] || EMPTY_RECORDS_ARRAY,
|
||||
(state: RootState) => state.medications.medications,
|
||||
],
|
||||
(records, medications) => {
|
||||
// 创建药物ID到药物的映射
|
||||
const medicationMap = new Map<string, Medication>();
|
||||
medications.forEach((med) => medicationMap.set(med.id, med));
|
||||
|
||||
// 格式化剂量
|
||||
const dosage = `${medication.dosageValue} ${medication.dosageUnit}`;
|
||||
// 转换为展示项
|
||||
const displayItems = records
|
||||
.map((record) => {
|
||||
const medication = medicationMap.get(record.medicationId);
|
||||
if (!medication) return null;
|
||||
|
||||
// 格式化剂量
|
||||
const dosage = `${medication.dosageValue} ${medication.dosageUnit}`;
|
||||
|
||||
// 提取并格式化为当地时间(HH:mm格式)
|
||||
// 服务端返回的是UTC时间,需要转换为用户本地时间显示
|
||||
const localTime = dayjs(record.scheduledTime).format('HH:mm');
|
||||
const scheduledTime = localTime || '00:00';
|
||||
|
||||
// 频率描述
|
||||
const frequency = medication.repeatPattern === 'daily' ? '每日' : '自定义';
|
||||
|
||||
return {
|
||||
id: record.id,
|
||||
name: medication.name,
|
||||
dosage,
|
||||
scheduledTime,
|
||||
frequency,
|
||||
status: record.status,
|
||||
recordId: record.id,
|
||||
medicationId: medication.id,
|
||||
image: medication.photoUrl ? { uri: medication.photoUrl } : undefined
|
||||
} as import('@/types/medication').MedicationDisplayItem;
|
||||
})
|
||||
.filter((item): item is import('@/types/medication').MedicationDisplayItem => item !== null);
|
||||
|
||||
// 提取并格式化为当地时间(HH:mm格式)
|
||||
// 服务端返回的是UTC时间,需要转换为用户本地时间显示
|
||||
const localTime = dayjs(record.scheduledTime).format('HH:mm');
|
||||
const scheduledTime = localTime || '00:00';
|
||||
|
||||
// 频率描述
|
||||
const frequency = medication.repeatPattern === 'daily' ? '每日' : '自定义';
|
||||
|
||||
return {
|
||||
id: record.id,
|
||||
name: medication.name,
|
||||
dosage,
|
||||
scheduledTime,
|
||||
frequency,
|
||||
status: record.status,
|
||||
recordId: record.id,
|
||||
medicationId: medication.id,
|
||||
image: medication.photoUrl ? { uri: medication.photoUrl } : undefined
|
||||
} as import('@/types/medication').MedicationDisplayItem;
|
||||
})
|
||||
.filter((item): item is import('@/types/medication').MedicationDisplayItem => item !== null);
|
||||
|
||||
// 排序:未服用的药品(upcoming、missed)优先,已服用的药品(taken、skipped)其次
|
||||
// 在同一组内,按计划时间升序排列
|
||||
return displayItems.sort((a, b) => {
|
||||
// 定义状态优先级:数值越小优先级越高
|
||||
const statusPriority: Record<MedicationStatus, number> = {
|
||||
'missed': 1, // 已错过 - 最高优先级
|
||||
'upcoming': 2, // 待服用
|
||||
'taken': 3, // 已服用
|
||||
'skipped': 4, // 已跳过
|
||||
};
|
||||
|
||||
const priorityA = statusPriority[a.status];
|
||||
const priorityB = statusPriority[b.status];
|
||||
|
||||
// 首先按状态优先级排序
|
||||
if (priorityA !== priorityB) {
|
||||
return priorityA - priorityB;
|
||||
// 排序:未服用的药品(upcoming、missed)优先,已服用的药品(taken、skipped)其次
|
||||
// 在同一组内,按计划时间升序排列
|
||||
return displayItems.sort((a, b) => {
|
||||
// 定义状态优先级:数值越小优先级越高
|
||||
const statusPriority: Record<MedicationStatus, number> = {
|
||||
'missed': 1, // 已错过 - 最高优先级
|
||||
'upcoming': 2, // 待服用
|
||||
'taken': 3, // 已服用
|
||||
'skipped': 4, // 已跳过
|
||||
};
|
||||
|
||||
const priorityA = statusPriority[a.status];
|
||||
const priorityB = statusPriority[b.status];
|
||||
|
||||
// 首先按状态优先级排序
|
||||
if (priorityA !== priorityB) {
|
||||
return priorityA - priorityB;
|
||||
}
|
||||
|
||||
// 状态相同时,按计划时间升序排列
|
||||
return a.scheduledTime.localeCompare(b.scheduledTime);
|
||||
});
|
||||
}
|
||||
|
||||
// 状态相同时,按计划时间升序排列
|
||||
return a.scheduledTime.localeCompare(b.scheduledTime);
|
||||
});
|
||||
};
|
||||
);
|
||||
|
||||
// ==================== Export ====================
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import AsyncStorage from '@/utils/kvStore';
|
||||
import { logger } from '@/utils/logger';
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import type { RootState } from './index';
|
||||
|
||||
// Tab 配置接口
|
||||
@@ -190,10 +190,15 @@ export const {
|
||||
|
||||
// Selectors
|
||||
export const selectTabBarConfigs = (state: RootState) => state.tabBarConfig.configs;
|
||||
export const selectEnabledTabs = (state: RootState) =>
|
||||
state.tabBarConfig.configs
|
||||
|
||||
// ✅ 使用 createSelector 进行记忆化,避免不必要的重渲染
|
||||
export const selectEnabledTabs = createSelector(
|
||||
[selectTabBarConfigs],
|
||||
(configs) => configs
|
||||
.filter(config => config.enabled)
|
||||
.sort((a, b) => a.order - b.order);
|
||||
.sort((a, b) => a.order - b.order)
|
||||
);
|
||||
|
||||
export const selectIsInitialized = (state: RootState) => state.tabBarConfig.isInitialized;
|
||||
|
||||
// 按 id 获取配置
|
||||
|
||||
Reference in New Issue
Block a user