element-plus表单rules类型安全的薄见

291 阅读2分钟

前言

在使用typescript时,所有可以直接输入任意字符串的地方,都可能是被改造的目标。
而element-plus表单中,就有有这么一个。el-form-itemprop属性。
如下方代码,prop="name"prop="detail.job"都可能被输入任意值,导致与rules定义的key不相符而验证功能失效。

<template>
  <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" class="m-auto my-10">
    <el-form-item label="name" prop="name">
      <el-input v-model="ruleForm.name" />
    </el-form-item>
    <el-form-item label="Job" prop="detail.job">
      <el-input v-model="ruleForm.name" />
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="submitForm(ruleFormRef)"> Submit </el-button>
    </el-form-item>
  </el-form>
</template>

<script lang="ts" setup>
import { reactive, ref } from "vue"
import type { FormInstance, FormRules } from "element-plus"
const ruleFormRef = ref<FormInstance>()
const ruleForm = reactive({
  name: "",
  detail: {
    job: "",
  },
})
const rules = reactive<FormRules<typeof ruleForm>>({
  name: [{ required: true, message: "请输入名称", trigger: "blur" }],
  "detail.job": [{ required: true, message: "请输入工作", trigger: "blur" }],
})

const submitForm = (formEl: FormInstance | undefined) => {
  if (!formEl) return
  formEl.validate((valid) => {
    if (valid) {
      console.log("submit!")
    } else {
      console.log("error submit!")
    }
  })
}
</script>

那么怎么改造呢?

看见旁边的v-model就不以输入任意值,因为它直接引用了ruleForm
那么同理,prop如何引用rules呢?
很容易想到,把rules的key转化为一个新的key映射的object,然后prop再引用这个新object.

1.尝试定义转化函数

function obj2keyObj<T extends object>(
  data: T
): { [Key in keyof T]: Key } {
  const obj: any = {}
  Object.keys(data).forEach((key) => (obj[key] = key))
  return obj
}

const rulesKeys=obj2keyObj(rules)

但发现有问题,怎么是可空的,并且没有设置rule的detail,也出现在结果里了。

image.png

2.尝试解决可空问题,修改rules定义方式

const rules = reactive({
  name: [{ required: true, message: "请输入名称", trigger: "blur" }],
  "detail.job": [{ required: true, message: "请输入工作", trigger: "blur" }],
} satisfies FormRules<typeof ruleForm>)

 function obj2keyObj<T extends object>(
  data: T
): { [Key in keyof T]: Key } {
  const obj: any = {}
  Object.keys(data).forEach((key) => (obj[key] = key))
  return obj
}

const rulesKeys=obj2keyObj(rules)

image.png

3.最后在模板里绑定就好

<template>
  <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" class="m-auto my-10">
    <el-form-item label="name" :prop="rulesKeys.name">
      <el-input v-model="ruleForm.name" />
    </el-form-item>
    <el-form-item label="Job" :prop="rulesKeys['detail.job']">
      <el-input v-model="ruleForm.name" />
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="submitForm(ruleFormRef)"> Submit </el-button>
    </el-form-item>
  </el-form>
</template>

总结

将需要输入rules key的prop,从直接输入字符串,改为引用一个key值object ,而此object由rules派生而来,所有保证了类型安全。

期待一个star
我的github: github.com/leafio

完整代码

<template>
  <el-form ref="ruleFormRef" :model="ruleForm" :rules="rules" class="m-auto my-10">
    <el-form-item label="name" :prop="rulesKeys.name">
      <el-input v-model="ruleForm.name" />
    </el-form-item>
    <el-form-item label="Job" :prop="rulesKeys['detail.job']">
      <el-input v-model="ruleForm.name" />
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="submitForm(ruleFormRef)"> Submit </el-button>
    </el-form-item>
  </el-form>
</template>

<script lang="ts" setup>
import { reactive, ref } from "vue"
import type { FormInstance, FormRules } from "element-plus"
const ruleFormRef = ref<FormInstance>()
const ruleForm = reactive({
  name: "",
  detail: {
    job: "",
  },
})
const rules = reactive({
  name: [{ required: true, message: "请输入名称", trigger: "blur" }],
  "detail.job": [{ required: true, message: "请输入工作", trigger: "blur" }],
} satisfies FormRules<typeof ruleForm>)

function obj2keyObj<T extends object>(
  data: T
): { [Key in keyof T]: Key } {
  const obj: any = {}
  Object.keys(data).forEach((key) => (obj[key] = key))
  return obj
}

const rulesKeys = obj2keyObj(rules)

const submitForm = (formEl: FormInstance | undefined) => {
  if (!formEl) return
  formEl.validate((valid) => {
    if (valid) {
      console.log("submit!")
    } else {
      console.log("error submit!")
    }
  })
}
</script>