异步组件、动态插槽

异步加载的组件

代码

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)

defineAsyncComponentVue 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 体积。

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

    loadingComponenterrorComponent给用户更好的体验。


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>
相关推荐
hamber10 分钟前
GoGBA 上架半年记
前端
IT_陈寒13 分钟前
Python的finally居然不等同于Go的defer,差点坑惨我
前端·人工智能·后端
恋猫de小郭15 分钟前
给 AI 的 Agent 实现指南,可控 Agent 的关键
前端·人工智能·ai编程
米码收割机18 分钟前
【Python】Flask+SQLite_web 宠物领养系统 (源码+文档)【独一无二】
前端·python·flask
寒水馨34 分钟前
macOS下载、安装 Tailwind CSS-v4.3.3(附安装包tailwindcss-macos-arm64)
前端·css·macos·tailwind css·utility-first·css 框架·实用优先
shawxlee1 小时前
vue3+axios挑战最简洁实用的配置封装+接口调用:请求拦截器、响应拦截器、通用请求方法(单个请求/并发请求/下载文件)、统一接口管理
前端·经验分享·vue·接口·axios·api
yuhaiqiang1 小时前
上线一个人静态网站需要多少钱,难不难?
前端·后端·程序员
宸津-代码粉碎机1 小时前
Jar热部署进阶实战|修复原生方案OOM与类冲突问题,生产级无BUG优化方案
java·大数据·服务器·开发语言·前端·人工智能·python
To_OC9 小时前
写了三遍 Todo List,我终于搞懂了 React 父子组件到底怎么通信
前端·javascript·react.js
勇往直前plus10 小时前
Vue3(篇一) 核心概念——响应式模板语法与组件基础
前端·javascript·vue.js