【发布时间】:2022-01-17 07:02:47
【问题描述】:
我正在寻找一种良好且可重用的方法来访问没有 typeErrors 的错误对象内可能存在的嵌套属性(嵌套对象和对象数组)。
我有一个createCompany 表单/页面,其中包含以下数据
data() {
return {
company: {
same_billing_address: true,
physical_address: {},
billing_address: {},
contacts: [
{
function: '',
first_name: '',
last_name: '',
phone: '',
gender: 'female',
email: '',
language: 'nl',
date_of_birth: '',
},
],
},
validationErrors: {},
}
}
表单本身是这样的
<form @submit.prevent="createCompany" @keydown.enter="$event.preventDefault()" class="divide-y">
<fieldset class="pb-6">
<header>
<h3 class="mb-3 text-lg leading-6 font-medium text-gray-900">{{ $tc('general', 1) }}</h3>
</header>
<div class="grid grid-cols-12 gap-x-6 gap-y-3">
<div class="col-span-12">
<InputWithButton :label="$tc('enterprise_number', 1)" buttonLabel="Get enterprise data" :onClick="getEnterpriseData" type="text" id="enterprise_number" v-model="company.enterprise_number" :error="validationErrors.enterprise_number" />
</div>
<div class="col-span-6">
<Input :label="$tc('business_name', 1)" type="text" id="companyName" v-model="company.business_name" :error="validationErrors.business_name" />
</div>
<div class="col-span-6">
<Input :label="$tc('legal_entity_type', 1)" type="text" id="companyType" v-model="company.legal_entity_type" :error="validationErrors.legal_entity_type" />
</div>
<div class="col-span-3">
<Input :label="$tc('phone', 1)" type="text" id="phone" v-model="company.phone" :error="validationErrors.phone" />
</div>
<div class="col-span-6">
<Input :label="$tc('email_address', 1)" type="text" id="email" v-model="company.email" :error="validationErrors.email" />
</div>
</div>
</fieldset>
<fieldset class="py-6">
<header>
<h3 class="mb-3 text-lg leading-6 font-medium text-gray-900">{{ $tc('physical_address', 1) }}</h3>
</header>
<div class="grid grid-cols-12 gap-x-6 gap-y-3">
<div class="col-span-8">
<Input :label="$tc('street', 1)" type="text" id="street" v-model="company.physical_address.street" :error="validationErrors.physical_address.street" />
</div>
<div class="col-span-2">
<Input :label="$tc('number', 1)" type="text" id="number" v-model="company.physical_address.number" :error="validationErrors.physical_address.number" />
</div>
<div class="col-span-2">
<Input :label="$tc('addition', 1)" optional type="text" id="addition" v-model="company.physical_address.addition" :error="validationErrors.physical_address.addition" />
</div>
<div class="col-span-8">
<SelectWithSearch :label="$tc('city', 1)" id="billing_address_postal_code_id" v-model="company.physical_address.postal_code_id" :options="cityOptions" displayProperty="display_name" valueProperty="id" :minLengthForDropdown="3" :error="validationErrors.physical_address.zip_city" />
</div>
<div class="col-span-4">
<Input :label="$tc('country', 1)" type="text" id="country" v-model="company.physical_address.country" :error="validationErrors.physical_address.country" />
</div>
</div>
</fieldset>
<fieldset class="py-6">
<header>
<h3 class="mb-3 text-lg leading-6 font-medium text-gray-900">{{ $tc('billing_address', 1) }}</h3>
</header>
<div class="grid grid-cols-12 gap-x-6 gap-y-3">
<div class="col-span-12">
<Checkbox :label="$tc('billing_same_as_physical', 1)" v-model="company.same_billing_address" :error="validationErrors.same_billing_address" />
</div>
<template v-if="!company.same_billing_address">
<div class="col-span-8">
<Input :label="$tc('street', 1)" type="text" id="street" v-model="company.billing_address.street" :error="validationErrors.billing_address.street" />
</div>
<div class="col-span-2">
<Input :label="$tc('number', 1)" type="text" id="number" v-model="company.billing_address.number" :error="validationErrors.billing_address.number" />
</div>
<div class="col-span-2">
<Input :label="$tc('addition', 1)" type="text" id="addition" v-model="company.billing_address.addition" :error="validationErrors.billing_address.addition" />
</div>
<div class="col-span-8">
<SelectWithSearch :label="$tc('city', 1)" id="billing_address_postal_code_id" v-model="company.billing_address.postal_code_id" :options="cityOptions" displayProperty="display_name" valueProperty="id" :minLengthForDropdown="3" :error="validationErrors.billing_address.zip_city" />
</div>
<div class="col-span-4">
<Input :label="$tc('country', 1)" type="text" id="country" v-model="company.billing_address.country" :error="validationErrors.billing_address.country" />
</div>
</template>
</div>
</fieldset>
<fieldset class="py-6">
<div class="flex justify-between mb-3">
<header>
<h3 class="text-lg leading-6 font-medium text-gray-900">{{ $tc('contact', company.contacts.length) }}</h3>
</header>
<button type="button" class="text-sm leading-6 font-medium text-blue-500 flex items-center" @click="addContact">{{ $tc('add', 1) }} {{ $tc('contact', 1).toLowerCase() }}</button>
</div>
<section class="space-y-6">
<div v-for="(contact, contactIdx) in company.contacts" :key="contactIdx">
<h4 v-show="company.contacts.length > 1" class="mb-3 text-sm leading-6 font-medium text-gray-500">
{{ $tc('contact', 1) }} {{ contactIdx + 1 }} <span @click="deleteContact(contactIdx)" class="text-blue-500 cursor-pointer select-none">({{ $tc('delete', 1) }})</span>
</h4>
<div class="grid grid-cols-12 gap-x-6 gap-y-3">
<div class="col-span-12">
<RadioButtonGroup :label="$tc('gender', 1)" :options="genderOptions" v-model="contact.gender" :error="contacts[contactIdx].gender" />
</div>
<div class="col-span-6">
<Input :label="$tc('first_name', 1)" type="text" id="first_name" v-model="contact.first_name" :error="contacts[contactIdx].first_name" />
</div>
<div class="col-span-6">
<Input :label="$tc('last_name', 1)" type="text" id="last_name" v-model="contact.last_name" :error="contacts[contactIdx].last_name" />
</div>
<div class="col-span-3">
<Input :label="$tc('phone', 1)" type="text" id="phone" v-model="contact.phone" :error="contacts[contactIdx].phone" />
</div>
<div class="col-span-6">
<Input :label="$tc('email_address', 1)" type="text" id="email" v-model="contact.email" :error="contacts[contactIdx].email" />
</div>
<div class="col-span-3">
<Input :label="$tc('date_of_birth', 1)" type="date" id="date_of_birth" v-model="contact.date_of_birth" :error="contacts[contactIdx].date_of_birth" />
</div>
<div class="col-span-9">
<Input :label="$tc('function', 1)" type="text" id="function" v-model="contact.function" :error="contacts[contactIdx].function" />
</div>
<div class="col-span-3">
<Select :label="$tc('language', 1)" id="languageOfContact" :options="languageOptions" displayProperty="display_name" valueProperty="name" v-model="contact.language" :error="contacts[contactIdx].language" />
</div>
</div>
</div>
</section>
</fieldset>
<fieldset class="pt-6">
<SubmitButton :label="$tc('create_company', 1)" submittingLabel="Creating company..." />
</fieldset>
</form>
在数据发送到后端之前对其进行验证
async createCompany() {
try {
await CreateCompanyValidationSchema.validate(this.company, { abortEarly: false });
console.log('all good');
} catch (err) {
console.log(err.inner);
err.inner.forEach((error) => {
this.validationErrors = { ...this.validationErrors, [error.path]: error.message };
});
}
}
我正在使用Yup 来验证表单。架构如下所示
export const CreateCompanyValidationSchema = yup.object().shape({
enterprise_number: yup.string(),
business_name: yup.string(),
legal_entity_type: yup.string(),
phone: yup.string().required(),
email: yup.string().required().email(),
language: yup.string().required(),
first_name: yup.string(),
last_name: yup.string(),
date_of_birth: yup.date(),
physical_address: yup.object({
street: yup.string().required(),
number: yup.string().required(),
addition: yup.string(),
zip_city: yup.string().required(),
country: yup.string().required(),
}),
same_billing_address: yup.boolean(),
billing_address: yup.object().when('same_billing_address', {
is: false,
then: yup.object({
street: yup.string().required(),
number: yup.string().required(),
addition: yup.string(),
zip_city: yup.string().required(),
country: yup.string().required(),
}),
}),
contacts: yup.array().of(
yup.object().shape({
gender: yup.string().required().oneOf(['male', 'female', 'other']),
first_name: yup.string().required(),
last_name: yup.string().required(),
phone: yup.string().required(),
email: yup.string().required().email(),
date_of_birth: yup.date().required(),
function: yup.string().required(),
language: yup.string().required().oneOf(['nl', 'fr', 'en']),
})
),
});
validationErrors 数据对象具有嵌套对象结构(physical_address 和 billing_address)和嵌套对象数组(contacts)。 validationErrors 对象一开始是空的。如果嵌套地址字段或联系人有效,则validationErrors 对象将没有任何嵌套属性。但在表单中,我正在访问 validationErrors.contacts[contactIdx].phone 或 validationErrors.billing_address.street 等子属性。这会导致错误,因为这些属性不存在。应对这种情况的最佳方法是什么?我正在寻找具有这种结构的多种表单的可重用解决方案。
【问题讨论】:
-
这是 Vue 2 应用程序,不是吗?您可以使用
?.可选导航运算符,但它在 Vue 2 模板中不起作用。可能不是直接访问validationErrors,而是使用包装Lodashget或类似的安全导航实用程序的方法 -
这是一个 vue 3 应用程序,我们希望避免使用 Lodash。使用
?.是validationErrors.billing_address.street的一个选项,但是联系人呢?我认为这是不可能的validationErrors.contacts?[contactIdx].phone -
Lodash 是最受欢迎的。应该有微小的替代品。
-
我会检查 lodash
get函数,看看写一个本机可重用函数 -
实现真的很简单,如果性能不优先,需要几行。
标签: javascript forms vue.js vuejs3 yup