Why Vue Component Design Becomes a Nightmare and How to Fix It
After three years of building Vue apps, the author reveals how naive component extraction leads to sprawling directories, tangled props and events, and mounting technical debt, then offers concrete strategies—clear responsibilities, minimal APIs, slots, and abstraction skills—to design maintainable, reusable components.
Component design is the heavy trouble spot in Vue projects.
1. Extracting components ≠ reorganizing folders
Many beginners think componentization just means extracting UI that repeats.
Resulting components often look like:
<code><!-- TextInput.vue -->
<template>
<input :value="value" @input="$emit('update:value', $event.target.value)" />
</template>
</code>When you need an input with an icon, you copy it:
<code><!-- IconTextInput.vue -->
<template>
<div class="icon-text-input">
<i class="icon" :class="icon" />
<input :value="value" @input="$emit('update:value', $event.target.value)" />
</div>
</template>
</code>Later you add validation, loading, tooltip, and the component list explodes:
TextInput.vue IconTextInput.vue ValidatableInput.vue LoadingInput.vue FormInput.vueEach component is a quick fix, not reusable.
2. Uncontrolled abstraction: reuse for reuse, nobody dares to use
Consider a “super complex” table component:
<code><CustomTable :columns="columns" :data="tableData" :show-expand="true" :enable-pagination="true" :custom-actions="['edit', 'delete']" />
</code>It’s marketed as a generic component, but in practice:
Some pages need only display, no actions, but the component forces actions.
Custom sorting logic is impossible because it’s hard‑coded.
Styling conflicts with other UI libraries.
Warnings flood the console, making debugging hard.
Consequently, developers copy the component and modify it locally.
3. Data flows down, events flow up: Do you really understand props and emit?
Vue’s one‑way data flow states that parent components pass data via props, and children notify parents via emit.
Parent → props → child, child → emit → parent.
In reality:
Props are passed through seven layers, making data origins obscure.
Children emit multiple events, and parents pass callbacks back.
Developers resort to
inject/provide,
ref, or an
eventBusto bypass the flow.
Example of deep nesting causing confusion:
<code><!-- Grandparent component -->
<template>
<PageWrapper>
<ChildComponent :formData="form" @submit="handleSubmit" />
</PageWrapper>
</template>
<!-- Child component -->
<template>
<Form :model="formData" />
<button @click="$emit('submit', formData)">Submit</button>
</template>
</code>When the child is wrapped further, you lose track of which component controls the data and events.
4. Technical debt explosion: fear of deleting or changing
Component directories look tidy, but most components share these traits:
Many props and events with unclear usage.
Comments claim usage on page A, but pages B, C, D also import it.
A tiny change can cause a butterfly effect across the system.
Developers end up copying components and adding a
V2suffix instead of refactoring.
<code>components/
├── Input.vue
├── InputV2.vue
├── InputWithTooltip.vue
├── InputWithValidation.vue
├── InputWithValidationV2.vue
└── ...
</code>“To let others maintain my code, I decide not to touch it.”
5. The core of component design is “abstraction ability”
Vue component design’s difficulty lies not in syntax or encapsulation, but in your ability to abstract problems.
Example: a search area component
Naïve implementation binds many props:
<code><SearchHeader :keyword="keyword" :startDate="start" :endDate="end" @search="search" />
</code>A better design provides slots and scoped slots:
<code><!-- SearchHeader.vue -->
<template>
<div class="search-header">
<slot name="form" />
<button @click="$emit('search')">Search</button>
</div>
</template>
<!-- Usage -->
<SearchHeader @search="search">
<template #form>
<el-input v-model="keyword" placeholder="Enter keyword" />
<el-date-picker v-model="range" type="daterange" />
</template>
</SearchHeader>
</code>“Give structure to components, give behavior to pages. Components collaborate instead of controlling everything.”
6. How to design components correctly?
I summarize three simple yet effective recommendations:
✅ 1. Clarify component responsibilities: UI, interaction, or logic?
UI components only handle presentation (buttons, tags, cards).
Interaction components encapsulate user actions (inputs, selectors).
Logic components encapsulate business rules (filters, paginators).
Avoid mixing UI, logic, and API calls in a single component.
✅ 2. Keep props and emits minimal, expose only what’s required
If a component has more than six props, reconsider its design.
Abstract generic events like
clickinto meaningful business events.
Do not use
refto manipulate child internals; it’s an anti‑pattern.
✅ 3. Use slots instead of overly customized props
When a component’s props become overly complex, replace them with slots:
<code><!-- Before -->
<SuperButton :label="'Submit'" :icon="'plus'" :iconPosition="'left'" :styleType="'primary'" :loading="true" />
<!-- After -->
<SuperButton>
<template #icon><PlusIcon /></template>
Submit
</SuperButton>
</code>“Assign structure to components, assign behavior to pages. Components should collaborate, not control everything.”
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.