我有一个在 Vue 2 中制作的复选框表单问题组件,我正在迁移到 Vue 3,因此希望尽可能简化它,并确保我已经利用了 Vue 3 的所有新功能。
经过大量的尝试和错误,我已经设法让它工作,但我是否过于复杂了?
该组件应使用 v-slot 提出问题并在“options”数组属性中给出答案。 它应该让用户选择尽可能多的适用答案,但如果他们选择“无”,则应该清除所有其他答案。 如果他们选择了“无”,那么他们会选择另一个答案,它应该清除“无”,这样就不再检查它。
父组件
<template>
<div>
<h2>title</h2>
<InputCheckboxGroup
name="question_one"
:required="checkIsRequired('question_one')"
:error="formRef.errors.get('question_one')"
v-model="formRef.question_one"
:options="getField('question_one')['options']"
@update:modelValue="handleAffectedByInput"
>
<template v-slot:question>
{{ getField("question_one").question }}
</template>
</InputCheckboxGroup>
</div>
</template>
<script setup>
import InputCheckboxGroup from "../Form/InputCheckboxGroup";
import Form from "../../../../Form";
import { isRequired } from "./formHelper.js";
import { ref, markRaw, defineExpose } from "vue";
const props = defineProps({
step: Object,
fields: Object,
formData: Object
})
let formRef = markRaw(ref(new Form({
question_one: []
}, props.formData)))
const getField = (fieldName) => {
return props.fields[fieldName];
}
const checkIsRequired = (fieldName) => {
var fieldData = getField(fieldName);
return isRequired(fieldData, formRef.value);
}
function handleAffectedByInput(values) {
if (values.length && (values[values.length - 1] === 'none')) {
formRef.question_one = [values[values.length - 1]];
return;
}
clearCheckboxValues(['none'], values, 'question_one');
}
function clearCheckboxValues(previousAnswers, values, formField) {
for (const answer of previousAnswers) {
if (values.includes(answer)) {
formRef.value[formField] = values.filter(value => value !== answer);
}
}
}
defineExpose({
formRef
})
</script>
子/复选框问题组件
<template>
<div class="form-group">
<fieldset :aria-describedby="name">
<legend>
<p v-if="$slots.question" class="input__question">
<slot name="question"></slot>
</p>
</legend>
<div
class="input_checkbox"
v-for="opt in options"
v-bind:key="opt.value"
>
<label v-if="opt.value == 'none'">
<input
type="checkbox"
value="none"
v-model="model"
@click="onCheckboxChange"
/>
<span>None</span>
</label>
<div v-else class="form-group form-check">
<input
type="checkbox"
class="form-check-input"
:value="opt.value"
@click="onCheckboxChange"
v-model="model"
/>
<label :for="opt.value" class="form-check-label">
{{ opt.label }}
</label>
</div>
</div>
</fieldset>
</div>
</template>
<script setup>
import { defineModel, defineEmits } from "vue";
const props = defineProps({
error: String,
name: String,
options: Array,
required: Boolean
});
const model = defineModel()
const emit = defineEmits(['update:modelValue'])
function optionIsChecked (value) {
return model.value.includes(value);
}
function onCheckboxChange($event) {
var previouslySelected = model.value || [];
var newValue = [];
if ($event.target.checked) {
newValue = [...previouslySelected, $event.target.value];
} else {
newValue = previouslySelected.filter(
(x) => x != $event.target.value
);
}
if ($event.target.value === 'none' || $event.target.value === 'involved_none_above') {
newValue = [$event.target.value];
}
model.value = newValue
emit("update:modelValue", newValue);
}
</script>
以最简单的形式,您所追求的内容如下:
const { createApp, ref, computed, watch } = Vue
createApp({
setup() {
const options = ['one', 'two', 'three', 'four']
const selection = ref([])
const isNoneSelected = computed({
get() {
return !selection.value.length
},
set() {
selection.value = []
}
})
return {
options,
selection,
isNoneSelected
}
}
}).mount('#app')
label input[type="radio"] {
pointer-events: none;
}
label {
display: block;
cursor: pointer;
}
label:hover {
background-color: #f5f5f5;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.5.4/vue.global.prod.min.js"></script>
<div id="app">
<label v-for="item in options" :key="item">
<input type="checkbox" v-model="selection" :value="item" />{{ item }}
</label>
<label>
<input type="checkbox" v-model="isNoneSelected" /> none
</label>
</div>
您也可以使用
reactive
代替 ref
:
const { reactive, toRefs } = Vue
//...
setup() {
const state = reactive({
options: ['one', 'two', 'three', 'four'],
selection: [],
isNoneSelected: computed({
get() {
return !state.selection.length
},
set() {
state.selection = []
}
})
})
return toRefs(state)
}
确实,同一件事有不同的语法。模板保持不变。