基础篇九 Nuxt4 插件系统:扩展 Nuxt 能力

文章目录

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 引擎等高级话题。

相关文章

入门篇三:Nuxt4组件自动导入:写代码少敲一半字

入门篇二:Nuxt 4路由自动生成:告别手动配置路由的日子

延伸阅读

nuxt4完整系列,持续更新中。。,欢迎来逛逛


内容有帮助?点赞、收藏、关注三连!评论区等你 💪

相关推荐
程序员小寒2 小时前
JavaScript设计模式(十):模板方法模式实现与应用
前端·javascript·设计模式·模板方法模式
Bigger2 小时前
第六章:我是如何剖析 Claude Code 的终端界面渲染原理的
前端·react.js·claude
Alvin千里无风2 小时前
ECharts 世界地图实现完整指南
前端·echarts
七月稻草人2 小时前
Spring Boot + Vue 3 全栈项目,内网穿透实现 HTTPS 公网访问,前后端分离部署方案
vue.js·spring boot·https
We་ct2 小时前
EventSource & WebSocket & HTTP
前端·javascript·网络·websocket·网络协议·http·面试
张风捷特烈2 小时前
GetX 之死 | 8 年从未用过,以后将不会再用
android·前端·flutter
冲浪中台2 小时前
20个常用的CSS知识点
前端·css
青枣八神2 小时前
如何让手机访问电脑本地的前端服务器网页(Vite等前端项目)
服务器·前端·web·手机访问
榴莲omega2 小时前
第14天:React 工程化与设计模式
前端·react.js·设计模式