<script setup> import { defineAsyncComponent, ref } from 'vue' import * as AllIcons from '@vicons/ionicons5' // 校验函数 const isValidIcon = (name) => Object.keys(AllIcons).includes(name) // 带校验的动态加载 const loadIcon = (name) => { if (!isValidIcon(name)) { console.error(`Invalid icon name: ${name}`) return defineAsyncComponent(() => import('./DefaultIcon.vue')) } return defineAsyncComponent(() => import(`@vicons/ionicons5/es/${name}`).then(module => ({ default: module[name] })) ) } const currentIcon = computed(() => loadIcon(backendIconName.value) ) 封装成use
时间: 2025-04-01 09:26:17 浏览: 50
<think>好的,我现在需要帮用户把之前提供的Vue组件代码封装成一个可复用的Composable,也就是Vue 3中的use函数。用户给出的代码已经实现了动态加载图标的功能,但现在是希望将其封装成use函数,方便在其他组件中复用。
首先,我需要理解用户提供的代码结构。原代码中,他们导入了defineAsyncComponent和ref,以及所有图标。然后定义了一个校验函数isValidIcon,用来检查传入的图标名是否有效。接着是loadIcon函数,根据图标名动态加载对应的组件,如果无效则加载默认图标。最后通过computed属性currentIcon来返回加载的组件。
现在要将其封装成use函数,通常Vue 3的Composable是以use开头的函数,比如useIconLoader。这个函数应该接收参数,比如后台传递的图标名称,然后返回相关的响应式变量和方法。
首先,我需要确定这个Composable需要接收哪些参数。从原代码看,主要需要的是backendIconName,这可能是一个ref或者字符串。不过考虑到响应式,应该接受一个ref作为参数,或者内部自己创建。
接下来,封装校验函数。原来的isValidIcon是检查传入的名称是否在AllIcons的键中。这部分可以保留在Composable内部,作为辅助函数。
然后,动态加载图标的逻辑也需要封装进去。loadIcon函数需要处理动态导入,并且返回一个异步组件。这部分可以移动到Composable内部,可能作为一个内部函数。
原代码中的currentIcon是一个computed属性,根据backendIconName.value动态加载图标。在Composable中,可能需要将其作为返回值的一部分,让组件可以访问到当前图标组件。
另外,错误处理也需要考虑,比如加载失败时使用默认图标。原代码中已经在loadIcon里处理了,可能需要保留这部分逻辑。
还需要考虑类型校验,比如确保传入的图标名称是有效的,但原代码已经通过isValidIcon实现了。
然后,思考如何将上述逻辑组织成一个use函数。函数的结构可能是这样的:
1. 导入必要的Vue API和图标库。
2. 定义useIconLoader函数,接收backendIconName参数。
3. 在函数内部定义isValidIcon。
4. 定义loadIcon方法,处理动态加载。
5. 使用computed来根据backendIconName的变化更新当前图标。
6. 返回currentIcon或者其他必要的值。
另外,是否需要让外部可以访问到校验函数?或者是否需要提供手动重新加载的方法?这取决于用户的需求,但原代码中没有提到,可能暂时不需要。
可能的实现步骤:
- 创建useIconLoader.js文件,导出useIconLoader函数。
- 函数内部导入AllIcons和defineAsyncComponent。
- 接收一个iconName的ref作为参数。
- 创建isValidIcon函数。
- 定义loadIcon函数,处理动态导入。
- 创建currentIcon的computed属性,依赖iconName的变化。
- 返回currentIcon,以便组件中使用。
然后,在组件中使用时,只需要调用useIconLoader,传入backendIconName的ref,然后绑定到组件上。
另外,需要注意路径是否正确。原代码中的动态导入路径是@vicons/ionicons5/es/${name},需要确保在封装后路径依然正确。可能需要保持这个路径,或者允许用户配置路径,但原代码中没有提到,所以暂时保持原样。
错误处理方面,原代码在loadIcon中如果发现无效名称,会加载默认图标。这里需要确保默认图标的路径正确,或者在Composable中允许用户自定义默认图标路径。但根据原代码,暂时保持默认路径为'./DefaultIcon.vue',可能需要用户自己确保该组件存在,或者提供选项参数。
另外,在Composable中是否需要处理异步加载的状态,比如加载中或加载错误?原代码中使用了Suspense,但在封装后,可能需要让用户自己处理,或者在Composable中返回加载状态。不过原示例中没有提到,可能暂时不考虑,保持简单。
最后,测试这个Composable是否正常工作,确保当iconName变化时,currentIcon能正确更新,并加载对应的图标。
总结步骤:
1. 创建useIconLoader函数,接收iconName的ref。
2. 在函数内部导入所有图标,并定义校验函数。
3. 定义动态加载函数,处理有效性和路径。
4. 创建computed属性,返回异步组件。
5. 返回currentIcon供组件使用。</think>---
### **Composable 封装实现**
以下是将原有逻辑封装为 Vue 3 Composable 的完整方案:
```typescript
// useDynamicIcon.ts
import { defineAsyncComponent, computed, type Ref } from 'vue'
import * as AllIcons from '@vicons/ionicons5'
interface IconLoaderOptions {
fallbackComponent?: string // 可配置的备用组件路径
enablePreCheck?: boolean // 是否启用预校验
}
export function useDynamicIcon(
iconName: Ref<string>,
options: IconLoaderOptions = {
fallbackComponent: './DefaultIcon.vue',
enablePreCheck: true
}
) {
// 校验逻辑复用
const isValidIcon = (name: string) =>
Object.keys(AllIcons).includes(name)
// 核心加载器
const loadIcon = (name: string) => {
if (options.enablePreCheck && !isValidIcon(name)) {
console.error(`[IconLoader] Invalid icon name: ${name}`)
return defineAsyncComponent(() => import(options.fallbackComponent!))
}
return defineAsyncComponent(() =>
import(`@vicons/ionicons5/es/${name}`)
.then(module => ({
default: module[name] || module.default
}))
.catch(() => import(options.fallbackComponent!))
)
}
// 响应式图标引用
const currentIcon = computed(() => loadIcon(iconName.value))
return {
currentIcon,
isValidIcon // 暴露校验方法供外部使用
}
}
```
---
### **使用示例**
在 Vue 组件中的调用方式:
```vue
<script setup lang="ts">
import { ref } from 'vue'
import { useDynamicIcon } from './useDynamicIcon'
const backendIconName = ref('WineOutline') // 来自后台的图标名称
const { currentIcon } = useDynamicIcon(backendIconName, {
fallbackComponent: './assets/MyFallbackIcon.vue'
})
</script>
<template>
<component :is="currentIcon" class="icon-style" />
</template>
```
---
### **进阶功能扩展**
#### **1. 类型增强**
创建类型声明文件 `iconTypes.d.ts`:
```typescript
// 自动生成所有可用图标名称类型
type IonIcons = keyof typeof import('@vicons/ionicons5')
declare const __ALL_ICON_NAMES: IonIcons[]
```
#### **2. 缓存机制**
```typescript
// 在 Composable 中添加缓存逻辑
const iconCache = new Map<string, Component>()
const loadIconWithCache = (name: string) => {
if (iconCache.has(name)) {
return iconCache.get(name)!
}
const component = loadIcon(name)
iconCache.set(name, component)
return component
}
```
---
### **工程化配置**
#### **vite.config.ts 优化**
```typescript
export default defineConfig({
resolve: {
alias: {
'@icons': path.resolve(__dirname, './src/assets/icons')
}
},
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('@vicons/ionicons5')) {
return 'ionicons'
}
}
}
}
}
})
```
---
### **性能监控(示例)**
```typescript
// 添加性能追踪
const startLoadTime = Date.now()
performance.mark('icon-load-start')
return defineAsyncComponent({
loader: () => import(`@vicons/ionicons5/es/${name}`),
delay: 200,
timeout: 3000,
onError: (error) => {
console.error(`[IconLoader] Load failed: ${error}`)
},
onLoad: () => {
performance.measure('icon-load-duration', 'icon-load-start')
}
})
```
---
### **实现原理图示**
```mermaid
graph TD
A[后台传递图标名称] --> B{校验有效性}
B -->|有效| C[动态加载对应图标]
B -->|无效| D[加载备用图标]
C --> E[缓存组件实例]
D --> F[返回备用组件]
E --> G[响应式更新视图]
F --> G
```
---
### **最佳实践建议
阅读全文