目录

铁打的程序员,流水的数据过滤查询表单(vue3+reactive踩坑)

一、前言

铁打的程序员,流水的数据过滤查询表单。hello大家好,下文以element-plus组件库为例,开发一个通过js数组配置虚拟dom模式,解放双手快速生成数据过滤表单,一定程度提升你的开发效率。除此之外,文末还通过这个例子引出reactive响应式api使用时的一些注意点,帮助大家在开发中少踩坑,早点下班~。

二、预期效果

下文以文本、数字、选择框三个控件来表单过滤表单,如有更多控件需求可自行扩展。

最终使用示例:

js 复制代码
<FilterForm :items="items" v-model="query" @submit="onSearch"></FilterForm>

// 过滤查询参数
const query = ref<{
  name?: string,
  gender?: number,
  age?: number,
  phone?: string,
  address?: string
}>({})

// 获取过滤数据
const onSearch = () => {
  getList(query.value)
}

const items = computed(() => {
  return [
    { label: '名称', prop: 'name' },
    { label: '性别', prop: 'gender', type: widget.select, options: genderList.value },
    { label: '年龄', prop: 'age', type: widget.number },
    { label: '手机号码', prop: 'phone' },
    { label: '地址', prop: 'address' },
  ]
})

如果想要给定默认值,可以直接在query对象中进行直接指定。

三、动态表单封装

1.FilterForm.vue

js 复制代码
<template>
  <el-form
    ref="formRef"
    :model="form"
    label-width="auto"
    class="demo-ruleForm"
    inline="inline"
    @submit.native.prevent
  >
    <el-form-item
      :label="item.label"
      :prop="item.prop"
      v-for="item in items"
      :key="item.prop"
      :label-width="item.labelWidth"
    >
      <el-input
        v-if="!item.type || item.type === widget.text"
        v-model="form[item.prop]"
        :placeholder="item.placeholder"
        :disabled="item.disabled"
        clearable
      />

      <el-input
        type="number"
        v-else-if="item.type === widget.number"
        v-model="form[item.prop]"
        :placeholder="item.placeholder"
        @input="(value: string) => form[item.prop] = Number(value)"
        :disabled="item.disabled"
      />

      <el-select
        v-else-if="item.type === widget.select"
        v-model="form[item.prop]"
        :value-key="item.rowKeys ? item.rowKeys.value : 'value'"
        :placeholder="item.placeholder"
        :disabled="item.disabled"
        clearable
      >
        <el-option
          :label="getLabel(item, option)"
          :value="getValue(item, option)"
          v-for="(option, i) in item.options"
          :key="i"
        />
      </el-select>
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="submit(formRef)" native-type="submit">
        搜索
      </el-button>
      <el-button @click="reset(formRef)">重置</el-button>
    </el-form-item>
  </el-form>
</template>

<script setup lang="ts">
import { ref, computed } from "vue";
import type { FormInstance } from "element-plus";
import type { Option, Item, ModelValue } from "./types";
import { widget } from "@/utils/widget";

const props = defineProps<{
  modelValue: ModelValue;
  items: Item[];
}>();
const emit = defineEmits<{
  (e: "submit"): void;
  (e: "update:modelValue"): void;
}>();

// form组件实列
const formRef = ref<FormInstance>();
// 表单数据
const form = computed({
  get: () => props.modelValue,
  set: (val) => {
    emit("update:modelValue", val);
  },
});

// 提交表单
const submit = async (formEl: FormInstance | undefined) => {
  if (!formEl) return;
  emit("submit");
};
// 重置表单
const reset = (formEl: FormInstance | undefined) => {
  if (!formEl) return;
  formEl.resetFields();
  emit("submit");
};

const getLabel = (item: Item, option: Option) => {
  return item.rowKeys
    ? option[item.rowKeys.label as keyof Option]
    : option.name;
};
const getValue = (item: Item, option: Option) => {
  return item.rowKeys ? option[item.rowKeys.value as keyof Option] : option.id;
};
</script>

<style scoped lang="scss"></style>

注意: 以上表单双向数据绑定使用了computedsetget进行拦截,当表单单个数据改变时调用emit("update:modelValue", val)更新父组件的query对象对应的数据,保持单向数据流的原则。

2.组件props参数类型

types.ts

ts 复制代码
export type ModelValue = { [key: string]: any };

export type Option = {
  id: number,
  name: string | number
}

export type Item = {
  label: string,
  prop: string,
  type?: number,
  placeholder?: string,
  disabled?: boolean,
  labelWidth?: string,
  options?: Option[],
  rowKeys?: { label: string, value: string }
}

3.组件控件枚举值

widget.ts

ts 复制代码
export enum widget {
  text = 1,
  select,
  number
}

组件控件的映射关系建议统一封装,避免使用硬编码的方式,保持可扩展性和灵活性。

四、扩展阅读reactive一些需要注意的点:

以下问题出现在子组件单独维护form表单数据对象时,当用户点击搜索时再一次性更新父组件的form表单对象。

父组件定义query表单对象时选择reactive时(const query = reactive({}))。

在子组件执行emit("update:modelValue", form)更新父组件数据(相当于@update:modelValue = "query = $event")时,会对整个query对象重新赋值。

问题1: 父组件使用const定义的query表单对象,那么将无法正常更新父组件的值,因为const无法重新赋值,而且子组件执行emit("update:modelValue", form)更新时,控制台也没有打印对常量重新赋值的错误信息。

问题2: 父组件使用let定义的query表单对象,那么重新赋值将失去原来响应式,导致一些奇异的bug。

当然你说有没有解决方案,也有,但是性价比没有使用ref高。

解决办法: 使用Object.assign合并对象,emit('update:modelValue', Object.assign(props.modelValue, form))

个人看法,既然知道存在一些不可预知的问题,何不避免使用它呢。

下图为vue官方文档中说明推荐使用 ref()作为声明响应式状态的主要 API 的原话,当然接不接受建议还是看个人选择(狗头.jpg):

往期文章回顾

table表格自适应浏览器窗口变化解决方案

一文学会vue3如何自定义hook钩子函数和封装组件

一文学会请求中断、请求重发、请求排队、请求并发

本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
腾讯TNTWeb前端团队17 分钟前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰4 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪4 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪4 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy5 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom5 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom5 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom5 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom5 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom6 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试