如何避免 vue 子组件中的数百个事件?
How to avoid hundreds of events in vue child components?
我有一个像
这样的对象
class Engine {
id = 0;
crankRPM: = 200;
maxRPM = 2400;
numCylinders = 8;
maxOilTemp = 125;
isRunning = false;
start() { ... }
stop() { ... }
}
现在我想要一个引擎组件。
Vue 不希望我在任何子组件中改变 属性 的状态,所以我现在必须这样做:
engine-ui 组件定义:
<template>
<card> // Imagine fancy styling here
{{ engine.id }}
<input :value="engine.crankRPM" @input="$emit('changeCrankRPM', $event.target.value)"></input>
<input :value="engine.maxRPM" @input="$emit('changeMaxRPM', $event.target.value)"></input>
<input :value="engine.numCylinders" @input="$emit('changeMumCylinders', $event.target.value)"></input>
<input :value="engine.maxOilTemp" @input="$emit('changeMaxOilTemp', $event.target.value)"></input>
<toggle type="toggle" :value="engine.isRunning" @input="$emit('changeIsRunning', $event.target.value) </toggle>
</card>
</template>
<script lang="ts">
import { Engine } from "src/code/Engine";
import { defineComponent } from "vue";
export default defineComponent({
name: "engine-ui",
props: {
engine: {
type: Engine,
required: true,
},
},
});
</script>
在父组件中的用法:
<template>
...
<engine-ui :engine="myEngine"
@changeCrankRPM="myEngine.crankRPM = $event.target.value"
@changeMAxRPM="myEngine.maxRPM = $event.target.value"
@changeNumCylinders="myEngine.numCylinders = $event.target.value"
@changeOilTemp="myEngine.maxOilTemp = $event.target.value"
@changeIsRunning="myEngine.isRunning = $event.target.value"/>
...
</template>
这非常冗长和笨拙。如果我在 Engine class 中还有另外 100 个字段,它将在使用 engine-ui 的每个父组件中变得不可读,因为它将是一堵巨大的文本墙。
设计这个的“正确方法”是什么?
如果我现在更改引擎 class 我必须更新引擎-ui 组件以及任何父组件中的每个事件,因为发出的是字符串。
为了使代码易于维护,可以将模板逻辑减少到最少。
惯用的方法是将engine
视为单个值并实现two-way绑定
对于 v-model
,它将是:
<engine-ui v-model="myEngine"/>
和
...
<div v-for="(_, field) in modelValue">
<input v-model="modelValue[field]"/>
...
props: {
modelValue: Engine
},
改变 prop 被认为不是一个好的做法,因为这会使数据流更加复杂。这可以通过在每次字段更改时从 child 发出克隆的 Engine
实例来克服,这可能会造成浪费。另一种方法是在 parent 中保持更新与现在相同:
...
<engine-ui :value="myEngine" @update="onEngineUpdate" />
...
methods: {
onEngineUpdate({ field, fieldValue }) {
this.myEngine[field] = fieldValue;
},
},
...
和
...
<div v-for="(fieldValue, field) in value">
<input :value="fieldValue" @input="$emit('update', { field, fieldValue : $event.target.value })" />
...
props: {
value: Engine
},
...
这是一个简单的示例,您只需列出字段类型即可。一切都通过 update
函数。但是你需要在那里小心并将值转换为正确的类型,因为原生 @input
或 @change
事件总是 return 值作为字符串。
但是,如您所见,这消除了为每个 prop 提供更新函数的冗长。
如果需要,它可以轻松扩展为自定义输入类型的自定义组件。
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>
我有一个像
这样的对象class Engine {
id = 0;
crankRPM: = 200;
maxRPM = 2400;
numCylinders = 8;
maxOilTemp = 125;
isRunning = false;
start() { ... }
stop() { ... }
}
现在我想要一个引擎组件。
Vue 不希望我在任何子组件中改变 属性 的状态,所以我现在必须这样做:
engine-ui 组件定义:
<template>
<card> // Imagine fancy styling here
{{ engine.id }}
<input :value="engine.crankRPM" @input="$emit('changeCrankRPM', $event.target.value)"></input>
<input :value="engine.maxRPM" @input="$emit('changeMaxRPM', $event.target.value)"></input>
<input :value="engine.numCylinders" @input="$emit('changeMumCylinders', $event.target.value)"></input>
<input :value="engine.maxOilTemp" @input="$emit('changeMaxOilTemp', $event.target.value)"></input>
<toggle type="toggle" :value="engine.isRunning" @input="$emit('changeIsRunning', $event.target.value) </toggle>
</card>
</template>
<script lang="ts">
import { Engine } from "src/code/Engine";
import { defineComponent } from "vue";
export default defineComponent({
name: "engine-ui",
props: {
engine: {
type: Engine,
required: true,
},
},
});
</script>
在父组件中的用法:
<template>
...
<engine-ui :engine="myEngine"
@changeCrankRPM="myEngine.crankRPM = $event.target.value"
@changeMAxRPM="myEngine.maxRPM = $event.target.value"
@changeNumCylinders="myEngine.numCylinders = $event.target.value"
@changeOilTemp="myEngine.maxOilTemp = $event.target.value"
@changeIsRunning="myEngine.isRunning = $event.target.value"/>
...
</template>
这非常冗长和笨拙。如果我在 Engine class 中还有另外 100 个字段,它将在使用 engine-ui 的每个父组件中变得不可读,因为它将是一堵巨大的文本墙。
设计这个的“正确方法”是什么?
如果我现在更改引擎 class 我必须更新引擎-ui 组件以及任何父组件中的每个事件,因为发出的是字符串。
为了使代码易于维护,可以将模板逻辑减少到最少。
惯用的方法是将engine
视为单个值并实现two-way绑定
对于 v-model
,它将是:
<engine-ui v-model="myEngine"/>
和
...
<div v-for="(_, field) in modelValue">
<input v-model="modelValue[field]"/>
...
props: {
modelValue: Engine
},
改变 prop 被认为不是一个好的做法,因为这会使数据流更加复杂。这可以通过在每次字段更改时从 child 发出克隆的 Engine
实例来克服,这可能会造成浪费。另一种方法是在 parent 中保持更新与现在相同:
...
<engine-ui :value="myEngine" @update="onEngineUpdate" />
...
methods: {
onEngineUpdate({ field, fieldValue }) {
this.myEngine[field] = fieldValue;
},
},
...
和
...
<div v-for="(fieldValue, field) in value">
<input :value="fieldValue" @input="$emit('update', { field, fieldValue : $event.target.value })" />
...
props: {
value: Engine
},
...
这是一个简单的示例,您只需列出字段类型即可。一切都通过 update
函数。但是你需要在那里小心并将值转换为正确的类型,因为原生 @input
或 @change
事件总是 return 值作为字符串。
但是,如您所见,这消除了为每个 prop 提供更新函数的冗长。
如果需要,它可以轻松扩展为自定义输入类型的自定义组件。
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>