refactor: 重构 RunScreen 组件,提取任务项为独立组件并优化性能,使用 useMemo 和 useCallback 提高渲染效率

This commit is contained in:
xudan 2025-07-23 17:58:27 +08:00
parent 97164ff220
commit cdffc19db1

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback } from 'react';
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import {
StyleSheet,
Text,
@ -25,16 +25,6 @@ const statusOptions = [
{ label: '已终止', value: TaskStatus.TERMINATED },
];
// 状态显示配置
const statusDisplayMap: { [key: number]: { text: string; color: string } } = {
[TaskStatus.WAITING]: { text: '待分配', color: '#9E9E9E' },
[TaskStatus.RUNNING]: { text: '执行中', color: '#2196F3' },
[TaskStatus.PAUSED]: { text: '已暂停', color: '#FF9800' },
[TaskStatus.COMPLETED]: { text: '已完成', color: '#4CAF50' },
[TaskStatus.FAILED]: { text: '已失败', color: '#F44336' },
[TaskStatus.TERMINATED]: { text: '已终止', color: '#9C27B0' },
};
interface TaskRunItem {
id: string;
parentId: string;
@ -56,184 +46,62 @@ interface TaskRunItem {
runDuration: number;
}
export default function RunScreen() {
const [tasks, setTasks] = useState<TaskRunItem[]>([]);
const [loading, setLoading] = useState(false);
const [refreshing, setRefreshing] = useState(false);
// 筛选状态
const [filterStatus, setFilterStatus] = useState<TaskStatus | undefined>(
undefined,
);
const [filterName, setFilterName] = useState('');
// 操作状态
const [operatingTaskId, setOperatingTaskId] = useState<string | null>(null);
// 批量操作状态
const [selectedTasks, setSelectedTasks] = useState<Set<string>>(new Set());
const [isSelectionMode, setIsSelectionMode] = useState(false);
const [batchOperating, setBatchOperating] = useState(false);
// 加载任务列表
const loadTasks = useCallback(
async (isRefresh = false) => {
if (isRefresh) {
setRefreshing(true);
} else {
setLoading(true);
}
try {
const params = {
status: filterStatus,
name: filterName.trim() || undefined,
pageNum: 1,
pageSize: 100,
};
const response = await getTaskRunList(params);
setTasks(response || []);
} catch (error) {
console.error('加载任务列表失败:', error);
Alert.alert('错误', '加载任务列表失败,请重试');
setTasks([]);
} finally {
setLoading(false);
setRefreshing(false);
}
},
[filterStatus, filterName],
);
// 初始加载
useEffect(() => {
loadTasks();
}, [loadTasks]);
// 刷新任务列表
const handleRefresh = () => {
loadTasks(true);
};
// 重置筛选
const handleResetFilter = () => {
setFilterStatus(undefined);
setFilterName('');
};
// 切换选择模式
const toggleSelectionMode = () => {
setIsSelectionMode(!isSelectionMode);
setSelectedTasks(new Set());
};
// 选择/取消选择任务
const toggleTaskSelection = (taskId: string) => {
const newSelection = new Set(selectedTasks);
if (newSelection.has(taskId)) {
newSelection.delete(taskId);
} else {
newSelection.add(taskId);
}
setSelectedTasks(newSelection);
};
// 全选/取消全选
const toggleSelectAll = () => {
if (selectedTasks.size === tasks.length) {
setSelectedTasks(new Set());
} else {
setSelectedTasks(new Set(tasks.map(task => task.id)));
}
};
// 批量终止任务
const handleBatchTerminate = async () => {
if (selectedTasks.size === 0) {
Alert.alert('提示', '请先选择要终止的任务');
return;
}
Alert.alert(
'确认操作',
`确定要终止选中的 ${selectedTasks.size} 个任务吗?`,
[
{ text: '取消', style: 'cancel' },
{
text: '确定',
style: 'destructive',
onPress: async () => {
setBatchOperating(true);
try {
const promises = Array.from(selectedTasks).map(taskId =>
terminateTask(taskId),
);
await Promise.all(promises);
Alert.alert('成功', `已成功终止 ${selectedTasks.size} 个任务`);
setSelectedTasks(new Set());
setIsSelectionMode(false);
loadTasks();
} catch (error) {
console.error('批量终止任务失败:', error);
Alert.alert('错误', '批量终止任务失败,请重试');
} finally {
setBatchOperating(false);
}
},
},
],
);
};
// 任务操作 - 只保留终止操作
const handleTaskOperation = async (taskId: string) => {
setOperatingTaskId(taskId);
try {
await terminateTask(taskId);
Alert.alert('成功', '终止任务成功');
// 重新加载任务列表
loadTasks();
} catch (error) {
console.error('终止任务失败:', error);
Alert.alert('错误', '终止任务失败,请重试');
} finally {
setOperatingTaskId(null);
}
};
// 格式化时间
const formatTime = (timeString?: string) => {
if (!timeString) return '--';
try {
return new Date(timeString).toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
});
} catch {
return timeString;
}
};
// 渲染任务项
const renderTaskItem = ({ item }: { item: TaskRunItem }) => {
// 提取任务项组件并使用 memo 优化
const TaskItem = React.memo<{
item: TaskRunItem;
isSelectionMode: boolean;
isSelected: boolean;
isOperating: boolean;
onToggleSelection: (taskId: string) => void;
onTaskOperation: (taskId: string) => void;
statusDisplayMap: { [key: number]: { text: string; color: string } };
}>(
({
item,
isSelectionMode,
isSelected,
isOperating,
onToggleSelection,
onTaskOperation,
statusDisplayMap,
}) => {
const statusInfo =
statusDisplayMap[item.status] || statusDisplayMap[TaskStatus.WAITING];
const isOperating = operatingTaskId === item.id;
const isSelected = selectedTasks.has(item.id);
// 格式化时间 - 移到组件内部避免重复计算
const formatTime = useCallback((timeString?: string) => {
if (!timeString) return '--';
try {
return new Date(timeString).toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
});
} catch {
return timeString;
}
}, []);
const handlePress = useCallback(() => {
if (isSelectionMode) {
onToggleSelection(item.id);
}
}, [isSelectionMode, onToggleSelection, item.id]);
const handleCheckboxPress = useCallback(() => {
onToggleSelection(item.id);
}, [onToggleSelection, item.id]);
const handleOperationPress = useCallback(() => {
onTaskOperation(item.id);
}, [onTaskOperation, item.id]);
return (
<TouchableOpacity
style={[styles.taskItem, isSelected ? styles.taskItemSelected : null]}
onPress={() => {
if (isSelectionMode) {
toggleTaskSelection(item.id);
}
}}
onPress={handlePress}
activeOpacity={isSelectionMode ? 0.7 : 1}
>
<View style={styles.taskHeader}>
@ -241,7 +109,7 @@ export default function RunScreen() {
{isSelectionMode && (
<TouchableOpacity
style={styles.checkboxContainer}
onPress={() => toggleTaskSelection(item.id)}
onPress={handleCheckboxPress}
>
<MaterialIcons
name={isSelected ? 'check-box' : 'check-box-outline-blank'}
@ -301,7 +169,7 @@ export default function RunScreen() {
item.status === TaskStatus.PAUSED) && (
<TouchableOpacity
style={[styles.actionButton, styles.terminateButton]}
onPress={() => handleTaskOperation(item.id)}
onPress={handleOperationPress}
disabled={isOperating}
>
{isOperating ? (
@ -318,7 +186,223 @@ export default function RunScreen() {
)}
</TouchableOpacity>
);
};
},
);
TaskItem.displayName = 'TaskItem';
export default function RunScreen() {
const [tasks, setTasks] = useState<TaskRunItem[]>([]);
const [loading, setLoading] = useState(false);
const [refreshing, setRefreshing] = useState(false);
// 筛选状态
const [filterStatus, setFilterStatus] = useState<TaskStatus | undefined>(
undefined,
);
const [filterName, setFilterName] = useState('');
// 操作状态
const [operatingTaskId, setOperatingTaskId] = useState<string | null>(null);
// 批量操作状态
const [selectedTasks, setSelectedTasks] = useState<Set<string>>(new Set());
const [isSelectionMode, setIsSelectionMode] = useState(false);
const [batchOperating, setBatchOperating] = useState(false);
// 使用 useMemo 缓存状态显示配置
const statusDisplayMap = useMemo(
() => ({
[TaskStatus.WAITING]: { text: '待分配', color: '#9E9E9E' },
[TaskStatus.RUNNING]: { text: '执行中', color: '#2196F3' },
[TaskStatus.PAUSED]: { text: '已暂停', color: '#FF9800' },
[TaskStatus.COMPLETED]: { text: '已完成', color: '#4CAF50' },
[TaskStatus.FAILED]: { text: '已失败', color: '#F44336' },
[TaskStatus.TERMINATED]: { text: '已终止', color: '#9C27B0' },
}),
[],
);
// 加载任务列表
const loadTasks = useCallback(
async (isRefresh = false) => {
if (isRefresh) {
setRefreshing(true);
} else {
setLoading(true);
}
try {
const params = {
status: filterStatus,
name: filterName.trim() || undefined,
pageNum: 1,
pageSize: 100,
};
const response = await getTaskRunList(params);
setTasks(response || []);
} catch (error) {
console.error('加载任务列表失败:', error);
Alert.alert('错误', '加载任务列表失败,请重试');
setTasks([]);
} finally {
setLoading(false);
setRefreshing(false);
}
},
[filterStatus, filterName],
);
// 初始加载
useEffect(() => {
loadTasks();
}, [loadTasks]);
// 刷新任务列表
const handleRefresh = useCallback(() => {
loadTasks(true);
}, [loadTasks]);
// 重置筛选
const handleResetFilter = useCallback(() => {
setFilterStatus(undefined);
setFilterName('');
}, []);
// 切换选择模式
const toggleSelectionMode = useCallback(() => {
setIsSelectionMode(!isSelectionMode);
setSelectedTasks(new Set());
}, [isSelectionMode]);
// 选择/取消选择任务
const toggleTaskSelection = useCallback((taskId: string) => {
setSelectedTasks(prev => {
const newSelection = new Set(prev);
if (newSelection.has(taskId)) {
newSelection.delete(taskId);
} else {
newSelection.add(taskId);
}
return newSelection;
});
}, []);
// 全选/取消全选
const toggleSelectAll = useCallback(() => {
if (selectedTasks.size === tasks.length) {
setSelectedTasks(new Set());
} else {
setSelectedTasks(new Set(tasks.map(task => task.id)));
}
}, [selectedTasks.size, tasks]);
// 批量终止任务
const handleBatchTerminate = useCallback(async () => {
if (selectedTasks.size === 0) {
Alert.alert('提示', '请先选择要终止的任务');
return;
}
Alert.alert(
'确认操作',
`确定要终止选中的 ${selectedTasks.size} 个任务吗?`,
[
{ text: '取消', style: 'cancel' },
{
text: '确定',
style: 'destructive',
onPress: async () => {
setBatchOperating(true);
try {
const promises = Array.from(selectedTasks).map(taskId =>
terminateTask(taskId),
);
await Promise.all(promises);
Alert.alert('成功', `已成功终止 ${selectedTasks.size} 个任务`);
setSelectedTasks(new Set());
setIsSelectionMode(false);
loadTasks();
} catch (error) {
console.error('批量终止任务失败:', error);
Alert.alert('错误', '批量终止任务失败,请重试');
} finally {
setBatchOperating(false);
}
},
},
],
);
}, [selectedTasks, loadTasks]);
// 任务操作 - 只保留终止操作
const handleTaskOperation = useCallback(
async (taskId: string) => {
setOperatingTaskId(taskId);
try {
await terminateTask(taskId);
Alert.alert('成功', '终止任务成功');
// 重新加载任务列表
loadTasks();
} catch (error) {
console.error('终止任务失败:', error);
Alert.alert('错误', '终止任务失败,请重试');
} finally {
setOperatingTaskId(null);
}
},
[loadTasks],
);
// 优化的渲染任务项函数
const renderTaskItem = useCallback(
({ item }: { item: TaskRunItem }) => {
const isOperating = operatingTaskId === item.id;
const isSelected = selectedTasks.has(item.id);
return (
<TaskItem
item={item}
isSelectionMode={isSelectionMode}
isSelected={isSelected}
isOperating={isOperating}
onToggleSelection={toggleTaskSelection}
onTaskOperation={handleTaskOperation}
statusDisplayMap={statusDisplayMap}
/>
);
},
[
operatingTaskId,
selectedTasks,
isSelectionMode,
toggleTaskSelection,
handleTaskOperation,
statusDisplayMap,
],
);
// 优化 keyExtractor
const keyExtractor = useCallback((item: TaskRunItem) => item.id, []);
// 渲染分隔符
const renderSeparator = useCallback(
() => <View style={styles.separator} />,
[],
);
// 渲染空列表组件
const renderEmptyComponent = useCallback(
() => (
<View style={styles.emptyContainer}>
<MaterialIcons name="assignment" size={48} color="#666" />
<Text style={styles.emptyText}></Text>
</View>
),
[],
);
return (
<View style={styles.container}>
@ -443,19 +527,26 @@ export default function RunScreen() {
<FlatList
data={tasks}
renderItem={renderTaskItem}
keyExtractor={item => item.id}
keyExtractor={keyExtractor}
refreshing={refreshing}
onRefresh={handleRefresh}
ItemSeparatorComponent={() => <View style={styles.separator} />}
ListEmptyComponent={() => (
<View style={styles.emptyContainer}>
<MaterialIcons name="assignment" size={48} color="#666" />
<Text style={styles.emptyText}></Text>
</View>
)}
ItemSeparatorComponent={renderSeparator}
ListEmptyComponent={renderEmptyComponent}
contentContainerStyle={
tasks.length === 0 ? styles.emptyList : undefined
}
// 性能优化配置
removeClippedSubviews={true}
maxToRenderPerBatch={10}
windowSize={10}
initialNumToRender={10}
updateCellsBatchingPeriod={50}
// 避免重复渲染
extraData={{
selectedTasks: selectedTasks.size,
isSelectionMode,
operatingTaskId,
}}
/>
)}
</View>
@ -508,6 +599,8 @@ const styles = StyleSheet.create({
paddingHorizontal: 12,
paddingVertical: 8,
borderRadius: 4,
height: 40,
justifyContent: 'center',
},
resetButtonText: {
color: '#ffffff',