1👍
Here’s a simple example where all you need to do is list the field types. Everything passess through the update
function. But you need to be careful there and cast the value to the correct type, as native @input
or @change
events always return the value as a string.
However, as you can see, this eliminates the verboseness of having an update function for each prop.
It can be easily expanded into rendering custom components for custom input types, should you need to.
const { defineComponent, createApp, toRefs, reactive } = Vue;
class Engine {
id = 0;
crankRPM = 200;
description = 'Some description';
maxRPM = 2400;
numCylinders = 8;
maxOilTemp = 125;
isRunning = false;
}
const app = createApp({
setup() {
const state = reactive({
engine: new Engine(),
engineFields: [
{ key: 'crankRPM', type: 'number' },
{ key: 'maxRPM', type: 'number' },
{ key: 'description', type: 'text' },
{ key: 'numCylinders', type: 'number' },
{ key: 'maxOilTemp', type: 'number' },
{ key: 'isRunning', type: 'boolean' }
]
})
const update = ({ value, field }) => {
state.engine[field.key] = field.type === 'number' ? +value : value;
};
return {
...toRefs(state),
update
}
}
}).mount('#app')
<script src="https://unpkg.com/vue@next/dist/vue.global.prod.js"></script>
<div id="app">
<div class="card">
{{ engine.id }}
<template v-for="field in engineFields" :key="field.key">
<label v-if="field.type === 'boolean'">
<input type="checkbox"
:checked="engine[field.key]"
@change="update({ value: $event.target.checked, field })">
{{ field.key }}
</label>
<input v-else
:type="field.type"
:value="engine[field.key]"
@input="update({ value: $event.target.value, field })">
<!-- you can have as many input types as you want,
just replace v-else above with v-else-if and add
another case (e.g: textarea, custom components, ...) -->
</template>
</div>
<pre v-text="engine" />
</div>
1👍
Template logic can to be reduced to a minimum in order to make the code easy to maintain.
The idiomatic way is to treat engine
as a single value and implement two-way binding
With v-model
, it would be:
<engine-ui v-model="myEngine"/>
and
...
<div v-for="(_, field) in modelValue">
<input v-model="modelValue[field]"/>
...
props: {
modelValue: Engine
},
It’s not considered a good practice to mutate a prop because this makes data flow more complex. This can be overcome by emitting cloned Engine
instance from a child on each fieid change, which can be wasteful. Another way is to keep updates in a parent the same they are now:
...
<engine-ui :value="myEngine" @update="onEngineUpdate" />
...
methods: {
onEngineUpdate({ field, fieldValue }) {
this.myEngine[field] = fieldValue;
},
},
...
and
...
<div v-for="(fieldValue, field) in value">
<input :value="fieldValue" @input="$emit('update', { field, fieldValue : $event.target.value })" />
...
props: {
value: Engine
},
...