插件系统
插件是用来为应用添加全局功能的机制。通过插件,你可以在应用挂载前注册指令、提供全局服务、扩展应用能力,而无需修改组件代码。
AppPlugin 类型
Vitarx 的插件有两种形式:函数式插件和对象式插件。
typescript
// 函数式插件:直接是一个函数
type AppPluginInstall<T = {}> = (app: App, options?: T) => void
// 对象式插件:带有 install 方法的对象
interface AppObjectPlugin<T = {}> {
install: AppPluginInstall<T>
}
// 插件联合类型
type AppPlugin<T = {}> = AppObjectPlugin<T> | AppPluginInstall<T>app.use — 安装插件
use 方法用于安装插件,支持函数式和对象式两种插件形式。返回应用实例,支持链式调用。
typescript
app.use<T extends {}>(plugin: AppPlugin<T>, options?: T): thisplugin:插件,可以是函数或带有install方法的对象options:可选的插件配置,会传递给插件的安装函数
tsx
import { createApp } from 'vitarx'
const app = createApp(App)
// 安装函数式插件
app.use(myPlugin, { prefix: 'my' })
// 安装对象式插件
app.use(routerPlugin, { routes: [...] })
// 链式安装
app
.use(pluginA)
.use(pluginB, { debug: true })函数式插件
函数式插件就是一个普通函数,接收 app 实例和可选的 options 配置参数。
tsx
import { createApp, type AppPlugin } from 'vitarx'
// 定义函数式插件
const i18nPlugin: AppPlugin<{
locale: string
messages: Record<string, Record<string, string>>
}> = (app, options) => {
const locale = options?.locale ?? 'zh-CN'
const messages = options?.messages ?? {}
// 通过 provide 提供全局翻译服务
app.provide('i18n', {
t(key: string) {
return messages[locale]?.[key] ?? key
},
locale
})
}
// 使用插件
const app = createApp(App)
app.use(i18nPlugin, {
locale: 'zh-CN',
messages: {
'zh-CN': { hello: '你好', goodbye: '再见' },
en: { hello: 'Hello', goodbye: 'Goodbye' }
}
})
app.mount('#app')对象式插件
对象式插件是一个带有 install 方法的对象。install 方法的签名与函数式插件相同。
tsx
import { createApp, type AppObjectPlugin } from 'vitarx'
// 定义对象式插件
const routerPlugin: AppObjectPlugin<{ routes: Route[] }> = {
install(app, options) {
const routes = options?.routes ?? []
// 创建路由实例
const router = createRouter(routes)
// 提供全局路由服务
app.provide('router', router)
// 注册路由相关的指令
app.directive('link', {
mounted(el, binding) {
el.addEventListener('click', () => {
router.push(binding.value)
})
}
})
}
}
// 使用插件
const app = createApp(App)
app.use(routerPlugin, {
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
})
app.mount('#app')对象式插件的
install方法在调用时会绑定到插件对象本身(this指向插件对象),方便在install中访问插件的其他属性。
插件的常见用途
插件通常用于以下场景:
| 用途 | 示例 |
|---|---|
| 注册全局指令 | app.directive('focus', focusDirective) |
| 提供全局服务 | app.provide('api', apiClient) |
| 注册路由 | app.provide('router', createRouter(routes)) |
| 状态管理 | app.provide('store', createStore()) |
| 国际化 | app.provide('i18n', createI18n(messages)) |
| 错误监控 | 在 app.config.errorHandler 中上报错误 |
编写自定义插件
下面通过一个完整的示例,展示如何编写一个实用的自定义插件。
示例:通知插件
tsx
import { createApp, inject, type AppPlugin, type Ref, ref } from 'vitarx'
// 通知项类型
interface Notification {
id: number
message: string
type: 'info' | 'success' | 'warning' | 'error'
}
// 通知服务类型
interface NotificationService {
notifications: Ref<Notification[]>
add(message: string, type?: Notification['type']): void
remove(id: number): void
}
// 创建通知服务
function createNotificationService(): NotificationService {
const notifications = ref<Notification[]>([])
let nextId = 0
const add = (message: string, type: Notification['type'] = 'info') => {
const id = nextId++
notifications.value = [...notifications.value, { id, message, type }]
// 3 秒后自动移除
setTimeout(() => {
remove(id)
}, 3000)
}
const remove = (id: number) => {
notifications.value = notifications.value.filter((n) => n.id !== id)
}
return { notifications, add, remove }
}
// 通知插件
const notificationPlugin: AppPlugin<{ maxCount?: number }> = (app, options) => {
const service = createNotificationService()
app.provide('notification', service)
}
// ============ 使用插件 ============
// 通知展示组件
function NotificationList() {
const service = inject<NotificationService>('notification')
if (!service) return null
return (
<div class="notification-container">
{service.notifications.value.map((n) => (
<div key={n.id} class={`notification notification-#123;n.type}`}>
{n.message}
<button onClick={() => service.remove(n.id)}>×</button>
</div>
))}
</div>
)
}
// 根组件
function App() {
const service = inject<NotificationService>('notification')
return (
<div>
<NotificationList />
<button onClick={() => service?.add('操作成功!', 'success')}>显示成功通知</button>
<button onClick={() => service?.add('请注意!', 'warning')}>显示警告通知</button>
</div>
)
}
// 创建应用并安装插件
const app = createApp(App)
app.use(notificationPlugin, { maxCount: 5 })
app.mount('#app')示例:全局错误上报插件
tsx
import { createApp, type AppPlugin } from 'vitarx'
// 错误上报插件
const errorReportPlugin: AppPlugin<{ endpoint: string }> = (app, options) => {
const endpoint = options?.endpoint ?? '/api/errors'
// 保存原始错误处理器
const originalHandler = app.config.errorHandler
// 替换全局错误处理器
app.config.errorHandler = (error, info) => {
// 上报错误到服务器
fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
source: info.source,
timestamp: Date.now()
})
}).catch(console.error)
// 仍然调用原始处理器
originalHandler(error, info)
}
}
// 使用
const app = createApp(App, {
errorHandler: (error, info) => {
console.error('[App]', error)
}
})
app.use(errorReportPlugin, { endpoint: '/api/error-report' })
app.mount('#app')完整示例
下面是一个展示插件系统核心用法的完整示例,包含函数式插件、对象式插件和链式安装:
tsx
import { createApp, inject, type AppPlugin, type AppObjectPlugin, ref } from 'vitarx'
// ============ 函数式插件:主题管理 ============
const themePlugin: AppPlugin<{ defaultTheme?: string }> = (app, options) => {
const theme = ref(options?.defaultTheme ?? 'light')
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}
app.provide('theme', theme)
app.provide('toggleTheme', toggleTheme)
}
// ============ 对象式插件:自动聚焦指令 ============
const directivesPlugin: AppObjectPlugin = {
install(app) {
// 自动聚焦指令
app.directive('focus', {
mounted(el: HTMLElement) {
el.focus()
}
})
// 点击外部指令
app.directive('click-outside', {
mounted(el: HTMLElement, binding) {
const handler = (e: Event) => {
if (!el.contains(e.target as Node)) {
binding.value()
}
}
document.addEventListener('click', handler)
// 将清理函数挂到元素上,方便卸载时移除
;(el as any)._clickOutsideHandler = handler
},
unmounted(el: HTMLElement) {
document.removeEventListener('click', (el as any)._clickOutsideHandler)
}
})
}
}
// ============ 使用插件 ============
function App() {
const theme = inject<import('vitarx').Ref<string>>('theme')
const toggleTheme = inject<() => void>('toggleTheme')
return (
<div class={`app theme-#123;theme?.value ?? 'light'}`}>
<h1>插件系统示例</h1>
<input v-focus placeholder="自动聚焦的输入框" />
<button onClick={toggleTheme}>切换主题(当前:{theme?.value})</button>
</div>
)
}
// 创建应用,链式安装插件
const app = createApp(App, {
errorHandler: (error, info) => {
console.error(`[#123;info.source}]`, error)
}
})
app.use(themePlugin, { defaultTheme: 'light' }).use(directivesPlugin).mount('#app')