表单输入性能组合式api优化实例
展示如何将现有代码重构为使用优化表单组合式API的版本。
首先,实现组合式函数
// src/composables/useOptimizedForm.ts
import { ref, watch, onMounted, Ref } from 'vue';
export function useOptimizedForm<T extends Record<string, any>>(
props: { [key: string]: any },
emit: any,
options: {
formDataProp: string;
updateEvent: string;
debounceTime?: number;
}
) {
const { formDataProp, updateEvent, debounceTime = 300 } = options;
// 创建本地表单数据副本
const localFormData = ref({ ...(props[formDataProp] || {}) }) as Ref<T>;
// 防抖定时器
let updateTimeout: number | null = null;
// 处理输入变化
const handleFieldChange = () => {
if (updateTimeout) clearTimeout(updateTimeout);
updateTimeout = window.setTimeout(() => {
emit(updateEvent, { ...localFormData.value });
updateTimeout = null;
}, debounceTime);
};
// 初始化表单数据
onMounted(() => {
if (props[formDataProp]) {
Object.assign(localFormData.value, props[formDataProp]);
}
});
// 监听外部数据变化,但避免循环更新
const isExternalUpdate = ref(false);
watch(() => props[formDataProp], (newVal) => {
if (newVal && !isExternalUpdate.value) {
isExternalUpdate.value = true;
// 使用setTimeout避免直接在watch回调中修改被监视的值
setTimeout(() => {
Object.assign(localFormData.value, newVal);
isExternalUpdate.value = false;
}, 0);
}
}, { deep: false });
return {
localFormData,
handleFieldChange
};
}
更具体的带编辑功能表单示例
以下是一个基于同样架构但包含编辑功能的表单组件示例:
<!-- src/components/EditableAppraisalForm.vue -->
<template>
<div class="editable-appraisal-form">
<div class="group-header">评价信息编辑</div>
<el-form ref="formRef" :model="localFormData" label-width="120px">
<el-form-item label="企事业单位名称" prop="operatorName">
<el-input
v-model="localFormData.operatorName"
@input="handleFieldChange"
placeholder="请输入企事业单位名称"
/>
</el-form-item>
<el-form-item label="最终评价得分" prop="finalEvaluationScore">
<el-input-number
v-model="localFormData.finalEvaluationScore"
:min="0"
:max="100"
:precision="2"
@change="handleFieldChange"
placeholder="请输入最终评价得分"
/>
</el-form-item>
<el-form-item label="最终评价等级" prop="finalEvaluationLevel">
<el-select
v-model="localFormData.finalEvaluationLevel"
@change="handleFieldChange"
placeholder="请选择最终评价等级"
style="width: 100%"
>
<el-option
v-for="item in finalLevelOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="评价意见" prop="evaluationComments">
<el-input
v-model="localFormData.evaluationComments"
type="textarea"
:rows="4"
@input="handleFieldChange"
placeholder="请输入评价意见"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSubmit">保存</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
<div v-if="scoreItems.length > 0" class="score-table-section">
<div class="group-header">评分项目编辑</div>
<el-table :data="scoreItems" border>
<el-table-column prop="name" label="评分项目" width="180" />
<el-table-column label="得分" width="180">
<template #default="{ row, $index }">
<el-input-number
v-model="row.score"
:min="0"
:max="row.maxScore"
:precision="2"
@change="() => handleScoreChange(row, $index)"
/>
</template>
</el-table-column>
<el-table-column prop="maxScore" label="满分" />
<el-table-column label="备注">
<template #default="{ row, $index }">
<el-input
v-model="row.comment"
@input="() => handleScoreChange(row, $index)"
/>
</template>
</el-table-column>
</el-table>
<div class="total-score">
总得分: {{ calculateTotalScore() }}
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { useOptimizedForm } from '@/composables/useOptimizedForm';
import { useGlobalDictsStoreWithOut } from '@/store/globalDicts';
// 定义接口
interface ScoreItem {
id: string;
name: string;
score: number;
maxScore: number;
comment: string;
}
interface FormData {
operatorName: string;
finalEvaluationScore?: number;
finalEvaluationLevel?: string;
evaluationComments: string;
lastUpdateTime?: string;
}
// Props和Emit定义
const props = defineProps<{
formData: FormData;
declareId: string;
}>();
const emit = defineEmits([
'update:formData',
'save-success',
'save-error'
]);
// 使用表单优化组合式API
const {
localFormData,
handleFieldChange
} = useOptimizedForm<FormData>(
props,
emit,
{
formDataProp: 'formData',
updateEvent: 'update:formData',
debounceTime: 300
}
);
// 获取字典数据
const globalDicts = useGlobalDictsStoreWithOut();
const finalLevelOptions = computed(() => globalDicts.dictMap.RDPJDJ || []);
// 评分项目
const scoreItems = ref<ScoreItem[]>([]);
const formRef = ref(null);
// 评分项变更处理函数 - 使用防抖
let scoreUpdateTimeout: number | null = null;
const handleScoreChange = (row: ScoreItem, index: number) => {
if (scoreUpdateTimeout) clearTimeout(scoreUpdateTimeout);
scoreUpdateTimeout = window.setTimeout(() => {
// 这里可以做数据验证
if (row.score > row.maxScore) {
row.score = row.maxScore;
ElMessage.warning(`得分不能超过满分${row.maxScore}`);
}
// 重新计算总分
const totalScore = calculateTotalScore();
localFormData.value.finalEvaluationScore = totalScore;
// 根据总分自动推荐评价等级
suggestEvaluationLevel(totalScore);
// 通知父组件更新
handleFieldChange();
scoreUpdateTimeout = null;
}, 300);
};
// 根据得分推荐评价等级
const suggestEvaluationLevel = (score: number) => {
if (!finalLevelOptions.value.length) return;
// 假设评价等级规则:
// 90-100: 优秀
// 80-89: 良好
// 60-79: 合格
// 0-59: 不合格
let level = '';
if (score >= 90) {
level = 'A'; // 假设A代表优秀
} else if (score >= 80) {
level = 'B'; // 假设B代表良好
} else if (score >= 60) {
level = 'C'; // 假设C代表合格
} else {
level = 'D'; // 假设D代表不合格
}
// 仅在未手动设置等级时自动设置
if (!localFormData.value.finalEvaluationLevel) {
localFormData.value.finalEvaluationLevel = level;
}
};
// 计算总分
const calculateTotalScore = () => {
let total = 0;
scoreItems.value.forEach(item => {
total += Number(item.score) || 0;
});
return parseFloat(total.toFixed(2));
};
// 提交表单
const handleSubmit = async () => {
if (!formRef.value) return;
try {
// 表单验证
await formRef.value.validate();
// 添加最后更新时间
localFormData.value.lastUpdateTime = new Date().toISOString();
// 构建保存数据结构
const saveData = {
...localFormData.value,
declareId: props.declareId,
scoreItems: scoreItems.value
};
// 这里模拟API调用
await new Promise(resolve => setTimeout(resolve, 800));
// 成功提示
ElMessage.success('保存成功');
// 触发成功事件
emit('save-success', saveData);
} catch (error) {
console.error('表单提交失败:', error);
ElMessage.error('表单验证失败,请检查输入');
emit('save-error', error);
}
};
// 重置表单
const handleReset = () => {
ElMessageBox.confirm('确定要重置表单吗?所有未保存的更改将被丢弃。', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 重置表单为初始状态
if (formRef.value) {
formRef.value.resetFields();
}
// 重新加载数据
loadFormData();
ElMessage.info('表单已重置');
}).catch(() => {
// 用户取消操作
});
};
// 加载数据
const loadFormData = async () => {
try {
// 模拟API调用获取评分项
await new Promise(resolve => setTimeout(resolve, 500));
// 模拟评分项数据
scoreItems.value = [
{ id: '1', name: '技术创新能力', score: 15, maxScore: 20, comment: '' },
{ id: '2', name: '产品质量', score: 18, maxScore: 20, comment: '' },
{ id: '3', name: '市场竞争力', score: 12, maxScore: 15, comment: '' },
{ id: '4', name: '经营管理', score: 14, maxScore: 15, comment: '' },
{ id: '5', name: '社会责任', score: 8, maxScore: 10, comment: '' },
{ id: '6', name: '发展潜力', score: 16, maxScore: 20, comment: '' }
];
// 初始计算总分
const totalScore = calculateTotalScore();
// 如果表单数据中没有评分,则使用计算得到的总分
if (!localFormData.value.finalEvaluationScore) {
localFormData.value.finalEvaluationScore = totalScore;
}
// 根据总分推荐评价等级
suggestEvaluationLevel(totalScore);
} catch (error) {
console.error('加载数据失败:', error);
ElMessage.error('加载数据失败');
}
};
// 初始化
onMounted(() => {
globalDicts.setDictMap();
loadFormData();
});
</script>
<style lang="less" scoped>
.editable-appraisal-form {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.group-header {
position: relative;
padding-left: 18px;
margin: 0 0 20px;
font-size: 16px;
font-weight: bold;
line-height: 16px;
&::after {
position: absolute;
top: 0;
left: 0;
display: block;
width: 6px;
height: 16px;
background-color: var(--el-color-primary);
content: '';
}
}
.score-table-section {
margin-top: 30px;
}
.total-score {
margin-top: 15px;
text-align: right;
font-size: 16px;
font-weight: bold;
}
</style>
如何使用该组件
<!-- 父组件 -->
<template>
<div class="appraisal-page">
<EditableAppraisalForm
:form-data="formData"
:declare-id="declareId"
@update:form-data="formData = $event"
@save-success="handleSaveSuccess"
@save-error="handleSaveError"
/>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import EditableAppraisalForm from '@/components/EditableAppraisalForm.vue';
const route = useRoute();
const declareId = ref(route.params.id || '');
// 初始表单数据
const formData = ref({
operatorName: '北京科技有限公司',
finalEvaluationScore: 0,
finalEvaluationLevel: '',
evaluationComments: ''
});
// 保存成功处理
const handleSaveSuccess = (data) => {
console.log('保存成功,数据:', data);
// 可以进行页面跳转或其他操作
};
// 保存失败处理
const handleSaveError = (error) => {
console.error('保存失败:', error);
// 可以进行错误处理
};
</script>
这个具体的例子展示了:
- 如何将组合式API应用到实际项目中
- 如何处理表单与表格交互
- 如何优化数据流向避免性能问题
- 如何使用防抖进行API调用优化
- 如何处理表单验证和提交
这个组件可以轻松集成到任何Vue 3项目中,优化表单性能,提供流畅的用户体验。
useOptimizedForm
组合式API分析
用途
这个组合式API主要用于解决Vue应用中表单性能问题,特别是:
- 优化表单输入响应速度:通过防抖机制减少更新频率
- 避免双向绑定循环更新:打破父子组件间可能形成的更新循环链
- 管理组件间数据同步:提供安全的数据流向管理机制
- 简化表单状态管理:封装常见的表单状态处理逻辑
使用场景
适用于以下典型场景:
- 复杂表单:包含多个输入项,需要频繁与父组件同步的表单
- 实时编辑表格:需要在表格中进行单元格编辑并实时同步
- 动态表单:表单结构和数据会根据用户操作动态变化
- 多层级组件通信:跨多层组件传递表单数据的场景
- 高频交互界面:用户会进行快速连续输入的界面
解决的问题
-
输入卡顿问题:
- 防止每次字符输入都触发完整的更新链
- 避免输入过程中DOM的过度重绘
-
数据丢失问题:
- 防止快速输入时因更新循环导致的字符丢失
- 避免外部数据覆盖用户未保存的输入
-
性能开销问题:
- 消除由
deep: true
监听引起的递归遍历开销 - 减少不必要的组件渲染
- 优化大型表单的CPU和内存占用
- 消除由
-
数据流向混乱:
- 建立清晰的数据流向和更新规则
- 防止多个监听器之间互相干扰
通用性评估
优势
- 框架无关性:可与任何UI组件库配合使用(Element Plus、Ant Design Vue、Vuetify等)
- 类型安全:支持TypeScript泛型,适应各种表单数据结构
- 配置灵活:可自定义防抖时间、更新事件名等参数
- 易于集成:不需要修改组件结构,只需添加几行代码
局限性
- 状态管理框架兼容:如果项目使用复杂的状态管理(如Vuex或Pinia),可能需要额外适配
- 表单验证集成:没有内置表单验证功能,需要与项目现有的验证系统配合
- 旧项目迁移:针对Vue 2项目,需要修改兼容Vue 3的组合式API
- 特殊表单控件:对于非标准输入控件(如富文本编辑器、特殊地图控件),可能需要自定义事件处理
扩展可能性
- 增加表单验证功能:可扩展支持同步和异步验证规则
- 增加持久化功能:添加表单数据本地存储能力
- 增加撤销/重做功能:通过记录操作历史支持表单编辑撤销
- 集成状态指示器:添加加载、保存状态的处理
总结
useOptimizedForm
组合式API提供了一种优雅且高效的方式来解决Vue应用中常见的表单性能问题。它足够通用,可以适应大多数表单场景,特别是对于中大型企业应用的复杂表单尤为有效。通过将表单处理逻辑抽象到可复用的组合式API中,不仅提高了代码的可维护性,也显著改善了用户体验。
虽然它可能不能解决所有特殊场景的需求,但作为一个基础构建块,可以根据项目特点进行定制和扩展,使其适应更多特定需求。