文章目录
Nuxt 的强大离不开插件系统。想用第三方库?需要全局注册组件?想监听应用生命周期?都需要插件。今天深入聊聊 Nuxt 插件系统的用法和最佳实践。
一、插件基础
插件放在 plugins 目录,自动注册:
ts
// plugins/my-plugin.ts
export default defineNuxtPlugin((nuxtApp) => {
console.log('插件执行了!')
// 这里的代码在应用创建时执行
})
nuxtApp 对象包含:
vueApp- Vue 应用实例provide- 提供全局方法hook- 监听生命周期runWithContext- 在应用上下文中执行
二、注入全局方法
ts
// plugins/utils.ts
export default defineNuxtPlugin(() => {
return {
provide: {
// 注入全局方法
formatDate: (date: Date) => {
return date.toLocaleDateString('zh-CN')
},
formatPrice: (price: number) => {
return `¥${price.toFixed(2)}`
}
}
}
})
使用:
vue
<script setup lang="ts">
const { $formatDate, $formatPrice } = useNuxtApp()
</script>
<template>
<p>{{ $formatDate(new Date()) }}</p>
<p>{{ $formatPrice(99.9) }}</p>
</template>
⚠️ 全局方法以
$开头,这是 Nuxt 的约定。
三、引入第三方库
示例:引入 dayjs
bash
pnpm add dayjs
ts
// plugins/dayjs.ts
import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn'
import relativeTime from 'dayjs/plugin/relativeTime'
dayjs.locale('zh-cn')
dayjs.extend(relativeTime)
export default defineNuxtPlugin(() => {
return {
provide: {
dayjs
}
}
})
使用:
vue
<script setup lang="ts">
const { $dayjs } = useNuxtApp()
</script>
<template>
<p>{{ $dayjs().format('YYYY-MM-DD') }}</p>
<p>{{ $dayjs('2024-01-01').fromNow() }}</p>
</template>
示例:引入 lodash
bash
pnpm add lodash-es
pnpm add -D @types/lodash-es
ts
// plugins/lodash.ts
import { debounce, throttle, cloneDeep } from 'lodash-es'
export default defineNuxtPlugin(() => {
return {
provide: {
debounce,
throttle,
cloneDeep
}
}
})
使用:
vue
<script setup lang="ts">
const { $debounce, $throttle } = useNuxtApp()
const handleSearch = $debounce((keyword: string) => {
console.log('搜索:', keyword)
}, 300)
</script>
四、生命周期钩子
插件可以监听 Nuxt 的生命周期:
ts
// plugins/lifecycle.ts
export default defineNuxtPlugin((nuxtApp) => {
// 应用创建完成
nuxtApp.hook('app:created', () => {
console.log('App created')
})
// 挂载前
nuxtApp.hook('app:beforeMount', () => {
console.log('Before mount')
})
// 挂载完成
nuxtApp.hook('app:mounted', () => {
console.log('Mounted')
})
// 页面切换前
nuxtApp.hook('page:start', () => {
console.log('Page start')
})
// 页面切换完成
nuxtApp.hook('page:finish', () => {
console.log('Page finish')
})
// 发生错误
nuxtApp.hook('app:error', (error) => {
console.error('App error:', error)
})
// Vue 错误
nuxtApp.hook('vue:error', (error, instance, info) => {
console.error('Vue error:', error)
})
})
五、全局组件注册
虽然 Nuxt 支持组件自动导入,但有些库需要手动注册:
ts
// plugins/components.ts
import MyButton from '~/components/MyButton.vue'
import MyModal from '~/components/MyModal.vue'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.component('MyButton', MyButton)
nuxtApp.vueApp.component('MyModal', MyModal)
})
六、全局指令
ts
// plugins/directives.ts
export default defineNuxtPlugin((nuxtApp) => {
// 点击外部指令
nuxtApp.vueApp.directive('click-outside', {
mounted(el, binding) {
el._clickOutside = (event: MouseEvent) => {
if (!(el === event.target || el.contains(event.target))) {
binding.value(event)
}
}
document.addEventListener('click', el._clickOutside)
},
unmounted(el) {
document.removeEventListener('click', el._clickOutside)
}
})
// 防抖指令
nuxtApp.vueApp.directive('debounce', {
mounted(el, binding) {
const delay = binding.arg ? parseInt(binding.arg) : 300
let timer: ReturnType<typeof setTimeout>
el._debounceHandler = () => {
clearTimeout(timer)
timer = setTimeout(() => {
binding.value()
}, delay)
}
el.addEventListener('click', el._debounceHandler)
},
unmounted(el) {
el.removeEventListener('click', el._debounceHandler)
}
})
})
使用:
vue
<template>
<div v-click-outside="handleClickOutside">
点击外部关闭
</div>
<button v-debounce="handleSubmit">提交</button>
<button v-debounce:500="handleSubmit">500ms 后执行</button>
</template>
七、插件执行顺序
插件按文件名字母顺序执行。需要控制顺序时:
ts
// plugins/01-first.ts
export default defineNuxtPlugin(() => {
console.log('第一个执行')
})
// plugins/02-second.ts
export default defineNuxtPlugin(() => {
console.log('第二个执行')
})
或者指定依赖:
ts
// plugins/second.ts
export default defineNuxtPlugin({
name: 'second',
dependsOn: ['first'], // 依赖 first 插件
setup() {
console.log('second 在 first 之后执行')
}
})
八、仅客户端/服务端执行
ts
// 仅客户端执行
export default defineNuxtPlugin({
name: 'client-only',
mode: 'client', // 或 environment: 'client'
setup() {
console.log('只在客户端执行')
}
})
// 仅服务端执行
export default defineNuxtPlugin({
name: 'server-only',
mode: 'server',
setup() {
console.log('只在服务端执行')
}
})
九、TypeScript 类型扩展
让 TypeScript 认识你注入的方法:
ts
// plugins/utils.ts
export default defineNuxtPlugin(() => {
return {
provide: {
formatDate: (date: Date) => date.toLocaleDateString('zh-CN')
}
}
})
// 声明类型
declare module '#app' {
interface NuxtApp {
$formatDate: (date: Date) => string
}
}
或者单独创建类型文件:
ts
// types/plugins.d.ts
declare module '#app' {
interface NuxtApp {
$dayjs: typeof import('dayjs')
$formatDate: (date: Date) => string
$formatPrice: (price: number) => string
}
}
export {}
十、页面切换进度条
使用插件实现全局 loading:
ts
// plugins/loading.ts
export default defineNuxtPlugin((nuxtApp) => {
const loading = ref(false)
nuxtApp.hook('page:start', () => {
loading.value = true
})
nuxtApp.hook('page:finish', () => {
setTimeout(() => {
loading.value = false
}, 200)
})
return {
provide: {
loading
}
}
})
vue
<!-- app.vue -->
<script setup lang="ts">
const { $loading } = useNuxtApp()
</script>
<template>
<div>
<div v-if="$loading" class="loading-bar">
<div class="progress"></div>
</div>
<NuxtPage />
</div>
</template>
总结
插件系统核心用法:
| 功能 | 实现方式 |
|---|---|
| 注入全局方法 | provide: { xxx } |
| 引入第三方库 | 在插件中导入并 provide |
| 生命周期监听 | nuxtApp.hook('xxx', fn) |
| 注册组件 | nuxtApp.vueApp.component() |
| 注册指令 | nuxtApp.vueApp.directive() |
| 控制执行顺序 | 文件名前缀或 dependsOn |
| 仅客户端/服务端 | mode: 'client' / 'server' |
基础篇到此结束!下一篇开始进入进阶篇,聊聊 SSR 原理、服务端渲染、Nitro 引擎等高级话题。
相关文章
延伸阅读
内容有帮助?点赞、收藏、关注三连!评论区等你 💪