【表单输入性能组合式api优化实例】

表单输入性能组合式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>

这个具体的例子展示了:

  1. 如何将组合式API应用到实际项目中
  2. 如何处理表单与表格交互
  3. 如何优化数据流向避免性能问题
  4. 如何使用防抖进行API调用优化
  5. 如何处理表单验证和提交

这个组件可以轻松集成到任何Vue 3项目中,优化表单性能,提供流畅的用户体验。

useOptimizedForm 组合式API分析

用途

这个组合式API主要用于解决Vue应用中表单性能问题,特别是:

  1. 优化表单输入响应速度:通过防抖机制减少更新频率
  2. 避免双向绑定循环更新:打破父子组件间可能形成的更新循环链
  3. 管理组件间数据同步:提供安全的数据流向管理机制
  4. 简化表单状态管理:封装常见的表单状态处理逻辑

使用场景

适用于以下典型场景:

  1. 复杂表单:包含多个输入项,需要频繁与父组件同步的表单
  2. 实时编辑表格:需要在表格中进行单元格编辑并实时同步
  3. 动态表单:表单结构和数据会根据用户操作动态变化
  4. 多层级组件通信:跨多层组件传递表单数据的场景
  5. 高频交互界面:用户会进行快速连续输入的界面

解决的问题

  1. 输入卡顿问题

    • 防止每次字符输入都触发完整的更新链
    • 避免输入过程中DOM的过度重绘
  2. 数据丢失问题

    • 防止快速输入时因更新循环导致的字符丢失
    • 避免外部数据覆盖用户未保存的输入
  3. 性能开销问题

    • 消除由deep: true监听引起的递归遍历开销
    • 减少不必要的组件渲染
    • 优化大型表单的CPU和内存占用
  4. 数据流向混乱

    • 建立清晰的数据流向和更新规则
    • 防止多个监听器之间互相干扰

通用性评估

优势

  1. 框架无关性:可与任何UI组件库配合使用(Element Plus、Ant Design Vue、Vuetify等)
  2. 类型安全:支持TypeScript泛型,适应各种表单数据结构
  3. 配置灵活:可自定义防抖时间、更新事件名等参数
  4. 易于集成:不需要修改组件结构,只需添加几行代码

局限性

  1. 状态管理框架兼容:如果项目使用复杂的状态管理(如Vuex或Pinia),可能需要额外适配
  2. 表单验证集成:没有内置表单验证功能,需要与项目现有的验证系统配合
  3. 旧项目迁移:针对Vue 2项目,需要修改兼容Vue 3的组合式API
  4. 特殊表单控件:对于非标准输入控件(如富文本编辑器、特殊地图控件),可能需要自定义事件处理

扩展可能性

  1. 增加表单验证功能:可扩展支持同步和异步验证规则
  2. 增加持久化功能:添加表单数据本地存储能力
  3. 增加撤销/重做功能:通过记录操作历史支持表单编辑撤销
  4. 集成状态指示器:添加加载、保存状态的处理

总结

useOptimizedForm组合式API提供了一种优雅且高效的方式来解决Vue应用中常见的表单性能问题。它足够通用,可以适应大多数表单场景,特别是对于中大型企业应用的复杂表单尤为有效。通过将表单处理逻辑抽象到可复用的组合式API中,不仅提高了代码的可维护性,也显著改善了用户体验。

虽然它可能不能解决所有特殊场景的需求,但作为一个基础构建块,可以根据项目特点进行定制和扩展,使其适应更多特定需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gazer_S

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值