异步组件、动态插槽

异步加载的组件

代码

typescript 复制代码
import { ref, defineAsyncComponent, watch, type Component } from 'vue'

interface TabsConfigItem {
    key: ENUM_DETAIL_TAB
    tab: string
    component: Component
}

// Tab 栏
export const enum ENUM_DETAIL_TAB {
    accountDetail = 'accountDetail',
    relatedTransaction = 'relatedTransaction',
    historicalCase = 'historicalCase',
    processLog = 'processLog'
}

/**
 * @constant tabsConfig
 * @description 动态组件的核心配置
 * 定义了每个 Tab 的唯一 key(引用枚举值)、显示的标题以及对应的异步加载组件。
 */
const tabsConfig: TabsConfigItem[] = [
    // 账户详情
    {
        key: ENUM_DETAIL_TAB.accountDetail,
        tab: riskManagementT('accountDetail'),
        component: defineAsyncComponent(() => import('./accountDetail.vue'))
    },
    // 关联交易
    {
        key: ENUM_DETAIL_TAB.relatedTransaction,
        tab: riskManagementT('relatedTransaction'),
        component: defineAsyncComponent(() => import('./relatedTransaction.vue'))
    },
    // 历史案件
    {
        key: ENUM_DETAIL_TAB.historicalCase,
        tab: riskManagementT('historicalCase'),
        component: defineAsyncComponent(() => import('./historicalCase.vue'))
    },
    // 处理日志
    {
        key: ENUM_DETAIL_TAB.processLog,
        tab: riskManagementT('processLog'),
        component: defineAsyncComponent(() => import('./processLog.vue'))
    }
]


// 从核心配置中派生出 Tab 列表和组件映射
const { tabList, tabComponentsMap } = tabsConfig.reduce(
    (acc, tab) => {
        const { key, tab: tabTitle, component } = tab
        acc.tabList.push({ key, tab: tabTitle })
        acc.tabComponentsMap[key] = component
        return acc
    },
    {
        tabList: [] as { key: ENUM_DETAIL_TAB; tab: string }[],
        tabComponentsMap: {} as Record<ENUM_DETAIL_TAB, Component>
    }
)

// 当前激活的 Tab,默认为账户详情
const activeKey = ref<ENUM_DETAIL_TAB>(ENUM_DETAIL_TAB.accountDetail)

defineAsyncComponent是 Vue 3 的 Composition API ​ 中,用来定义异步组件的函数。

它的作用可以概括为:让组件在需要时才加载,而不是一开始就打包进主包,从而减小首屏体积、提升加载速度。


1. 基本作用

  • 把组件变成一个异步加载的组件:

    在路由或父组件渲染时,才会去请求并加载这个组件的代码。

  • 内部基于 动态 import() ​ 实现,配合构建工具(Vite/Webpack)自动做代码分割(code splitting) 。


2. 基本用法

javascript 复制代码
import { defineAsyncComponent } from 'vue'

// 方式1:简单动态 import
const AsyncComp = defineAsyncComponent(() => import('./MyComponent.vue'))

// 方式2:带加载/错误状态的配置
const AsyncComp = defineAsyncComponent({
  loader: () => import('./MyComponent.vue'),
  loadingComponent: LoadingSpinner,   // 加载中显示的组件
  errorComponent: ErrorDisplay,       // 加载失败时显示的组件
  delay: 200,                         // 延迟多少 ms 才显示 loading
  timeout: 3000                       // 超时时间
})

3. 使用场景

  • 路由懒加载:

    javascript 复制代码
    const routes = [
      {
        path: '/dashboard',
        component: () => import('./views/Dashboard.vue')
      }
    ]

    在路由配置里,其实也是用了类似的异步组件思想。

  • 大型页面/低频功能:

    比如"报表弹窗""富文本编辑器"等,只有用户点开时才加载,减少首屏 JS 体积。

  • 需要加载状态/错误处理时:

    用 loadingComponent和 errorComponent给用户更好的体验。


4. 和 import()的区别

  • import()返回的是 Promise<Component>,需要你自己处理加载/错误状态。
  • defineAsyncComponent是对 import()的封装,帮你更方便地配置加载中、错误、超时等行为,并且在 Vue 组件树里使用时更符合 Vue 的生命周期管理。

✅ 一句话总结:

defineAsyncComponent的作用是 把组件变成按需异步加载的组件,既能减小首屏体积,又能优雅处理加载中和加载失败的情况,是 Vue 3 性能优化的重要工具。


动态插槽

通过 v-for 循环动态生成所有需要批量输入的具名插槽。#[item.field] 是 Vue 的动态插槽名语法,它会根据 item.field 的值(如 'icAccountList')来决定要渲染哪个插槽,从而避免了在模板中重复编写每个插槽的 template。

原代码

xml 复制代码
 <!-- 发卡账户 -->
            <template #icAccount="{ model, field }">
                <a-input-group compact>
                    <a-input
                        v-model:value.trim="model[field]"
                        :placeholder="dataT('icAccount')"
                        allow-clear
                    />
                    <a-button type="primary" class="border-5" @click="openBatchInputModal(field)">
                        <template #icon>
                            <PlusCircleOutlined />
                        </template>
                    </a-button>
                </a-input-group>
            </template>
            <!-- 企业账号 -->
            <template #opIdList="{ model, field }">
                <a-input-group compact>
                    <a-input
                        v-model:value.trim="model[field]"
                        :placeholder="dataT('opIds')"
                        allow-clear
                    />
                    <a-button type="primary" class="border-5" @click="openBatchInputModal(field)">
                        <template #icon>
                            <PlusCircleOutlined />
                        </template>
                    </a-button>
                </a-input-group>
            </template>

优化后

ini 复制代码
 <template
                v-for="item in editingList"
                :key="item.field"
                #[item.field]="{ model, field }"
            >
                <a-input-group compact>
                    <a-input
                        v-model:value.trim="model[field]"
                        :placeholder="item?.placeholder"
                        allow-clear
                        @input="handleInput($event, field)"
                    />
                    <a-button type="primary" class="border-5" @click="openBatchInputModal(field)">
                        <template #icon>
                            <PlusCircleOutlined />
                        </template>
                    </a-button>
                </a-input-group>
            </template>
相关推荐
念何架构之路3 分钟前
zap扩展生态与总结
java·前端·数据库
独孤九剑打醒他18 分钟前
【原创开源】【概念设计】源 - 栅 - 漏 - 栅 - 源 横向双栅 MOS,低压交流多值逻辑芯片探索
前端·其他·架构·开源·硬件工程
mantou1321 小时前
我给 AI Agent 做了个「油猴」:让 Claude Code / Codex 直接用你已登录的浏览器
前端·javascript·后端
默_笙1 小时前
🍕 一个主编、三个工种、两本手册:搭一支 AI 调研队
前端·javascript
纸片人1 小时前
Blender 建模 + Three.js 展示:和 AI 一起做一个光储充超充站数字孪生大屏
前端
智能直播1 小时前
网络RTMP拉流不卡顿、声音不同步?一文讲透缓冲区、时间戳与MEDAI V2实战调优
前端
第七页独白1 小时前
汽车零件厂如何通过 QMS 真正落地 IATF 16949——QMS软件系统:品质检验-内审稽核-8d客诉管理:全星质量管理软件系统
java·前端·数据库
纸片人1 小时前
只用 three.js + OpenStreetMap,手搓一个「成都城市 3D」数据大屏
前端
星若生辉1 小时前
Hugo 静态站点搭建|从本地调试到上线部署实操指南
前端
沐浴露z1 小时前
Agent 响应延迟过高?从工具治理角度提供两条思路
前端·网络