UniApp 表单校验两种方式对比:命令式与声明式

发布于:2025-03-26 ⋅ 阅读:(37) ⋅ 点赞:(0)

前言

🤟 找工作,来万码优才:👉 #小程序://万码优才/r6rqmzDaXpYkJZF

以下主要针对Demo讲解,从实战中的体会

何为命令式 何为声明式

  • 命令式的体验,随时都会有提交的按钮,但是点击提交才会显示不满足的条件!
    在这里插入图片描述
  • 声明式的体验,不满足条件时,按钮框是灰色的!
    在这里插入图片描述
  1. 命令式:
    提交逻辑复杂,需要异步校验(如服务端唯一性检查)
    表单字段多,依赖用户行为触发验证
    需要复用校验函数,或表单逻辑分散多处

  2. 声明式:
    表单简单明了
    用户体验优先,提前告知无法提交的情况
    状态可视化,一目了然

对比项 命令式校验(方法中校验) 声明式校验(computed + disabled 控制)
📋 方式 在 submit() 方法内通过 validateForm() 显式校验 通过 computed 计算属性实时判断是否可提交
🧠 编程范式 命令式(Imperative) 声明式(Declarative)
💡 表达方式 手动控制流程:如果失败就 return false 自动计算状态:按钮根据是否满足条件自动禁用
🔁 可复用性 校验逻辑聚焦在 validateForm(),但要手动调用 校验逻辑绑定在状态中,按钮等 UI 自动响应
🧪 用户体验 用户点击“提交”后才提示不通过 一目了然,提交按钮禁用,无法误触提交
⚙️ 灵活性 可以灵活插入额外逻辑,如提交前弹窗确认 逻辑适合纯状态驱动,复杂流程需另外处理
🪛 适用场景 需要流程控制、嵌套逻辑、异步校验时更适合 表单项简单明确、状态驱动时更适合

1. 实战

实战中抽取的Demo比较简易:

命令式 submit 校验方式(validateForm)

<template>
  <button type="primary" @click="submit">提交</button>
</template>

<script>
export default {
  data() {
    return {
      imgCntrF: [],
      damPhotoList: []
    };
  },
  methods: {
    validateForm() {
      if (!this.imgCntrF.length) {
        uni.showToast({ title: '请拍摄箱门照片', icon: 'none' });
        return false;
      }
      if (this.damPhotoList.length < 2) {
        uni.showToast({ title: '请至少拍摄 2 张照片', icon: 'none' });
        return false;
      }
      return true;
    },
    async submit() {
      if (!this.validateForm()) return;
      // 执行提交逻辑
      console.log("提交成功");
    }
  }
}
</script>

声明式 computed 控制按钮状态

<template>
  <button type="primary" :disabled="!canSubmit" @click="submit">提交</button>
</template>

<script>
export default {
  data() {
    return {
      photoList: {
        door: '',  // 对应 imgCntrF
        side: ''
      },
      damPhotoList: [],
      photoField: [
        { key: 'door', label: '箱门照片' },
        { key: 'side', label: '侧面照片' }
      ]
    };
  },
  computed: {
    canSubmit() {
      return this.photoField.every(field => this.photoList[field.key]) &&
             this.damPhotoList.length >= 2;
    }
  },
  methods: {
    submit() {
      console.log("提交成功");
    }
  }
}
</script>

2. Demo

以UniApp( Vue2 语法 + script 写法)

命令式校验提交(Imperative)

<template>
  <view class="container">
    <view class="section">
      <text>箱门照片:</text>
      <button @click="selectBoxDoorPhoto">选择照片</button>
      <image v-if="imgCntrF" :src="imgCntrF" class="img-preview" />
    </view>

    <view class="section">
      <text>破损照片:</text>
      <button @click="addDamPhoto">添加照片</button>
      <view class="photo-list">
        <image v-for="(photo, index) in damPhotoList" :key="index" :src="photo" class="img-preview" />
      </view>
    </view>

    <button type="primary" @click="submit">提交</button>
  </view>
</template>

<script>
export default {
  data() {
    return {
      imgCntrF: '', // 箱门照片 URL
      damPhotoList: [] // 破损照片 URL 列表
    };
  },
  methods: {
    selectBoxDoorPhoto() {
      uni.chooseImage({
        count: 1,
        success: (res) => {
          this.imgCntrF = res.tempFilePaths[0];
        }
      });
    },
    addDamPhoto() {
      uni.chooseImage({
        count: 1,
        success: (res) => {
          this.damPhotoList.push(res.tempFilePaths[0]);
        }
      });
    },
    validateForm() {
      if (!this.imgCntrF) {
        uni.showToast({ title: '请拍摄箱门照片', icon: 'none' });
        return false;
      }
      if (this.damPhotoList.length < 2) {
        uni.showToast({ title: '请至少拍摄 2 张破损照片', icon: 'none' });
        return false;
      }
      return true;
    },
    submit() {
      if (!this.validateForm()) return;

      // 模拟提交成功
      uni.showToast({ title: '提交成功', icon: 'success' });
    }
  }
};
</script>

<style>
.container {
  padding: 20rpx;
}
.section {
  margin-bottom: 30rpx;
}
.img-preview {
  width: 200rpx;
  height: 200rpx;
  margin-top: 10rpx;
}
.photo-list {
  display: flex;
  flex-wrap: wrap;
  gap: 10rpx;
}
</style>

声明式按钮控制提交(Declarative)

<template>
  <view class="container">
    <view class="section">
      <text>箱门照片:</text>
      <button @click="selectBoxDoorPhoto">选择照片</button>
      <image v-if="photoList.door" :src="photoList.door" class="img-preview" />
    </view>

    <view class="section">
      <text>破损照片:</text>
      <button @click="addDamPhoto">添加照片</button>
      <view class="photo-list">
        <image v-for="(photo, index) in damPhotoList" :key="index" :src="photo" class="img-preview" />
      </view>
    </view>

    <button type="primary" :disabled="!canSubmit" @click="submit">提交</button>
  </view>
</template>

<script>
export default {
  data() {
    return {
      photoList: {
        door: '' // 箱门照片
      },
      damPhotoList: []
    };
  },
  computed: {
    canSubmit() {
      return !!this.photoList.door && this.damPhotoList.length >= 2;
    }
  },
  methods: {
    selectBoxDoorPhoto() {
      uni.chooseImage({
        count: 1,
        success: (res) => {
          this.photoList.door = res.tempFilePaths[0];
        }
      });
    },
    addDamPhoto() {
      uni.chooseImage({
        count: 1,
        success: (res) => {
          this.damPhotoList.push(res.tempFilePaths[0]);
        }
      });
    },
    submit() {
      uni.showToast({ title: '提交成功', icon: 'success' });
    }
  }
};
</script>

<style>
.container {
  padding: 20rpx;
}
.section {
  margin-bottom: 30rpx;
}
.img-preview {
  width: 200rpx;
  height: 200rpx;
  margin-top: 10rpx;
}
.photo-list {
  display: flex;
  flex-wrap: wrap;
  gap: 10rpx;
}
</style>