You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue.js/vue-router:创建带Props的单文件组件实例及列表抽象优化

Hey there! Let's break down how to streamline your Vue code by optimizing that list abstraction you're working on. From what you've described—having a base List.vue with async data loading, plus multiple derived components that wrap it and pass props—this is the perfect spot to decouple concerns and cut down on repetitive code. I’ve used this exact pattern in several Vue projects, so let’s walk through the optimized structure and implementation.

Optimizing Vue List Component Abstraction & Code Simplification

Core Problem to Solve

Right now, your derived components are essentially just thin wrappers around the base List.vue that pass props for data loading. This creates unnecessary redundant code. The goal is to completely separate list UI rendering from data loading logic using a plugin-based structure, making your code more modular and maintainable.

Here’s a clean, scalable folder structure to organize your refactored code:

  • src/
    • components/
      • BaseList.vue
    • plugins/
      • listLoader/
        • index.js
        • loaders/
          • userLoader.js
          • productLoader.js
          • // Add more loaders for other business entities as needed
        • mixins/
          • listLoaderMixin.js
    • views/
      • UserListPage.vue
      • ProductListPage.vue

Step-by-Step Implementation

1. Refactor BaseList.vue to Be UI-Only

Strip all async loading logic from your original List.vue—it should only handle rendering based on props passed to it. Use slots to let business pages customize list item content:

<template>
  <div class="base-list">
    <div v-if="loading" class="loading-spinner">Loading data...</div>
    <div v-else-if="error" class="error-message">{{ error }}</div>
    <div v-else-if="!items.length" class="empty-state">No items found</div>
    <ul v-else class="list-container">
      <li v-for="item in items" :key="item.id" class="list-item">
        <slot name="item" :item="item"></slot>
      </li>
    </ul>
  </div>
</template>

<script setup>
defineProps({
  items: {
    type: Array,
    default: () => []
  },
  loading: {
    type: Boolean,
    default: false
  },
  error: {
    type: String,
    default: ''
  }
});
</script>

2. Build the List Loader Plugin

Create a plugin to centralize your loading logic and make it reusable across components. This keeps your data fetching code organized and accessible:

// src/plugins/listLoader/index.js
import listLoaderMixin from './mixins/listLoaderMixin';
import userLoader from './loaders/userLoader';
import productLoader from './loaders/productLoader';

export default {
  install(app) {
    // Register global mixin for loading state management
    app.mixin(listLoaderMixin);
    // Attach loaders to global properties for easy access
    app.config.globalProperties.$listLoaders = {
      user: userLoader,
      product: productLoader
    };
  }
};

3. Create Reusable Data Loaders

Each loader is a pure async function focused solely on fetching its respective data. No component-specific logic here:

// src/plugins/listLoader/loaders/userLoader.js
export default async function userLoader(params = {}) {
  const response = await fetch(`/api/users?${new URLSearchParams(params)}`);
  if (!response.ok) throw new Error('Failed to load user data');
  return response.json();
}

4. Shared Loading State Mixin

This mixin handles the repetitive loading/error/empty state logic so you don’t have to rewrite it in every page:

// src/plugins/listLoader/mixins/listLoaderMixin.js
export default {
  data() {
    return {
      listLoading: false,
      listError: '',
      listItems: []
    };
  },
  methods: {
    async loadListData(loader, params = {}) {
      this.listLoading = true;
      this.listError = '';
      try {
        this.listItems = await loader(params);
      } catch (err) {
        this.listError = err.message;
      } finally {
        this.listLoading = false;
      }
    }
  }
};

5. Use in Business Pages

Now you can ditch those redundant derived components. Instead, combine BaseList with the appropriate loader directly in your page components:

<template>
  <BaseList 
    :items="listItems" 
    :loading="listLoading" 
    :error="listError"
  >
    <template #item="{ item }">
      <div class="user-item">
        <h3>{{ item.fullName }}</h3>
        <p>{{ item.email }}</p>
        <button @click="viewUser(item.id)">View Details</button>
      </div>
    </template>
  </BaseList>
</template>

<script setup>
import BaseList from '@/components/BaseList.vue';
import { onMounted } from 'vue';

// Use global loader or import directly
const userLoader = inject('$listLoaders').user;

onMounted(() => {
  // Load data on component mount with optional params
  loadListData(userLoader, { page: 1, limit: 20 });
});

const viewUser = (userId) => {
  // Handle user detail navigation
};
</script>

Key Benefits of This Approach

  • Full Decoupling: List UI and data logic are completely separate, so BaseList can be reused anywhere you need a list view
  • Less Redundancy: No more creating dozens of similar XxxList.vue components—pages only handle their specific business logic
  • Better Maintainability: Data fetching logic lives in one place; updating a data endpoint only requires changing one loader file
  • Scalability: Adding a new list type is as simple as creating a new loader and using it in a page

内容的提问来源于stack exchange,提问作者товіаѕ

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 08:28:57