Vue:访问错误对象中可能存在的嵌套属性的最佳方法
Posted
技术标签:
【中文标题】Vue:访问错误对象中可能存在的嵌套属性的最佳方法【英文标题】:Vue: Best approach to access possible existing nested properties in error object 【发布时间】: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,而是使用包装Lodash get
或类似的安全导航实用程序的方法
这是一个 vue 3 应用程序,我们希望避免使用 Lodash。使用?.
是validationErrors.billing_address.street
的一个选项,但是联系人呢?我认为这是不可能的validationErrors.contacts?[contactIdx].phone
Lodash 是最受欢迎的。应该有微小的替代品。
我会检查 lodash get
函数,看看写一个本机可重用函数
实现真的很简单,如果性能不优先,需要几行。
【参考方案1】:
一种选择是使用短路安全访问嵌套属性,前提是它们存在。
if ( validationErrors && validationErrors.billing_address )
console.log(validationErrors.billing_address.street)
if 语句仅在 validationErrors.billing_address
已定义时为真,但在 validationErrors
未定义时不会抛出错误。
如果您认为这种模式过于丑陋和重复,您可能会对使用库来帮助您访问嵌套属性感兴趣。 https://lodash.com/docs/#get
【讨论】:
【参考方案2】:在 Vue 3 中,可以直接在模板中使用可选链(安全导航)运算符来访问不存在的键:
:error="contacts?.[contactIdx]?.first_name"
在 Vue 2 中,这需要将此代码移动到一个方法中,并可能使用字符串指定嵌套属性的路径,例如使用 Lodash get
或类似的辅助函数,它还允许指定不存在的默认值键。
【讨论】:
以上是关于Vue:访问错误对象中可能存在的嵌套属性的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章