插件系统

插件是用来为应用添加全局功能的机制。通过插件,你可以在应用挂载前注册指令、提供全局服务、扩展应用能力,而无需修改组件代码。

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): this
  • plugin:插件,可以是函数或带有 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')

下一步