使用模块和 Pinia 构建 Nuxt 3 应用

2025-06-07

使用模块和 Pinia 构建 Nuxt 3 应用

在 Nuxt 3 中构建Hello World应用程序相对简单,但你很可能会到达项目中的一个阶段,需要对应用程序进行结构化,以便在未来升级和添加新功能时轻松实现自定义和扩展。为此,你可以利用 的概念Modules

这篇文章的灵感来自@davorminchorov。谢谢你推荐这个主题,我在写的过程中学到了很多东西 :)

模块

模块用于扩展 Nuxt 核心功能。这些模块可以包含自己的组件、可组合项、页面、插件,甚至服务器中间件。通过所有这些模块化功能,我们可以轻松地绑定特定业务领域的上下文(有点像领域驱动设计,您可以在奖励链接中了解更多信息)。请查看此信息图,了解更多关于如何在 Nuxt 应用中评估模块的信息。(这是针对 Nuxt 2 的,但在 Nuxt 3 中工作原理非常相似。关于 Nuxt 3 模块的文档可能很快就会发布)。

Nuxt 模块

您可以在此处阅读有关模块的更多信息

Nuxt 3 示例

现在,我们知道了模块是什么,让我们深入研究代码,看看如何利用它们来构建我们的应用程序。

如果您在某个时候迷路了,您可以查看我为本文创建的 Github 存储库,其中包含本教程中涵盖的所有步骤 -> https://github.com/Baroshem/nuxt3-structure-modules-pinia

设置 Nuxt 3 样板项目

让我们从生成一个空的 Nuxt 3 项目开始。我们可以在终端中输入以下命令来执行此操作:



npx nuxi init nuxt3-pinia


Enter fullscreen mode Exit fullscreen mode

当您在代码编辑器中打开新创建的项目时,您应该看到以下结果:

VS Code 中的 Nuxt 3 项目

现在,让我们安装项目的依赖项:



yarn # npm install


Enter fullscreen mode Exit fullscreen mode

并启动项目以查看它是否按预期工作:



yarn dev # npm run dev


Enter fullscreen mode Exit fullscreen mode

如果一切顺利,我们应该在浏览器中看到以下结果:

浏览器中的 Nuxt 3

添加 Pinia

现在,我们已经有一个正在运行的样板项目,让我们在其中添加一个Pinia商店。如果您还没有尝试过 Pinia,我强烈建议您尝试一下。

Pinia is a store library for Vue, it allows you to share a state across components/pages.

既然我们已经知道什么是 Pinia,让我们深入研究代码并将其添加到 Nuxt 3 项目中。

首先,让我们安装@pinia/nuxtpinia



yarn add @pinia/nuxt pinia


Enter fullscreen mode Exit fullscreen mode

接下来,将添加@pinia/nuxtmodulesnuxt.config.ts



// nuxt.config.ts

import { defineNuxtConfig } from 'nuxt'

export default defineNuxtConfig({
  modules: [
    '@pinia/nuxt'
  ]
})


Enter fullscreen mode Exit fullscreen mode

为了测试 Pinia 是否按预期工作,让我们创建一个简单的商店



// store/test.ts

import { defineStore } from 'pinia'

export const useTest = defineStore({
  id: 'test',

  state: () => ({
    value: 1
  }),

  getters: {
    valueWithName: state => `Value is ${state.value}`
  },

  actions: {
    setNewValue(newValue: number) {
      this.value = newValue
    }
  }
})


Enter fullscreen mode Exit fullscreen mode

类似地,与 Vuex 在 Vue 和 Nuxt 2 中的使用方式一样,这里我们有一个初始的stategettersactions

  • state是一种可在整个应用程序内共享的反应状态,并且在全局更新时会进行相应的修改
  • getters用于获取某些状态值(也可以与文本或计算值等其他内容结合使用)
  • actions用于修改状态的初始值

现在,让我们检查一下我们新创建的商店是否已注册,以及我们是否可以访问app.vue



// app.vue

<template>
  <div>
    {{ test.value }}
    <NuxtWelcome />
  </div>
</template>

<script setup lang="ts">
import { useTest } from "~/store/test";
const test = useTest()
</script>


Enter fullscreen mode Exit fullscreen mode

检查浏览器以查看结果。

Pinia 存储价值

太棒了,成功了!

新的博客模块

为了本教程的目的,我们将创建一个简单的博客模块,其中包含它自己的组件、页面、可组合项和 Pinia 商店。

但首先,让我们删除之前创建的store/test.ts文件,并从中删除它的声明,因为app.vue我们不再需要它了。我们还删除NuxtWelcome组件,并添加一个NuxtPage如下所示的组件:



<template>
  <div>
    <NuxtPage/>
  </div>
</template>


Enter fullscreen mode Exit fullscreen mode

为了使此功能正常工作,我们必须pages在根项目中有一个目录(pages定义目录时会自动注册 NuxtPage)。

对于本教程,我们不需要其中的任何页面,因此我们可以将其留空(在代码存储库中它包含一个简单的.gitkeep文件)。

现在,我们将进入/blog目录并创建组件、可组合项、页面和存储。

成分

在本教程中,我们创建一个简单的 Vue 3 组件,BlogPost它将接受博客内容并将其显示在div



// modules/blog/components/BlogPost.vue

<template>
  <div>
    {{ blog }}
  </div>
</template>

<script setup lang="ts">
const props = defineProps({
  blog: {
    type: String,
    required: true
  }
})
</script>


Enter fullscreen mode Exit fullscreen mode

可组合项

为了管理整个应用程序的状态,我们可以使用 Pinia 或可组合项,但对于本教程,我想展示您可以同时使用这两种解决方案,或者选择最适合您的解决方案。



// modules/blog/composables/useBlog.ts

import { useState } from "#app"

export const useBlog = () => {
  const blogPostId = useState('blog-post-id', () => 1)

  return {
    blog: `Test blog post ${blogPostId.value}`
  }
}


Enter fullscreen mode Exit fullscreen mode

店铺

这与我们之前创建的一个非常相似的商店,用于测试 Pinia 是否正常工作。在本例中,我们将商店id及其名称修改为useBlogStore



// modules/blog/store/stores.ts

import { defineStore } from 'pinia'

export const useBlogStore = defineStore({
  id: 'blog-store',

  state: () => ({
    value: 1
  }),

  getters: {
    valueWithName: state => `Value is ${state.value}`
  },

  actions: {
    setNewValue(newValue: number) {
      this.value = newValue
    }
  }
})


Enter fullscreen mode Exit fullscreen mode

页面

在此页面中,我们将使用之前创建的可组合项、组件和 Pinia 存储来从路由器显示此内容和当前博客 ID 的值。



// modules/blog/pages/blog/[id].vue

<template>
  <section>
    <p>Blog post with the id: {{ currentRoute.params.id }}</p>
    <BlogPost :blog="blog"/>
    <span>Current value from blogStore: {{ blogStore.value }}</span>
  </section>
</template>

<script setup lang="ts">
import { useBlog } from '../../composables/useBlog';
import BlogPost from '../../components/BlogPost.vue';
import { useBlogStore } from '../../store/store';

const { currentRoute } = useRouter()
const { blog } = useBlog()
const blogStore = useBlogStore()
</script>


Enter fullscreen mode Exit fullscreen mode

这里发生的情况是我们正在导入:

  • useBlog可组合并blog从中解构属性。
  • useBlogStorePinia 商店让我们可以进入该状态。
  • BlogPost组件,我们将一个blog属性从useBlog可组合组件直接传递给它。

通过结合这三者,我们可以在页面上显示/blog/:id具有当前 id 的文本,这要归功于路由器、使用可组合和组件的博客内容以及来自 Pinia 商店的值。

将所有内容包装到模块中

上一步已经有点难了,所以这里我会尽量简化。为了让上一步在我们的 Nuxt 3 应用中可用,我们来创建一个模块,将组件、可组合项、页面和存储封装到一个易于导入的模块中。



// modules/blog/module.ts

import { defineNuxtModule } from '@nuxt/kit'
import { resolve, join } from 'pathe'
import type { Nuxt } from '@nuxt/schema'

export default defineNuxtModule({
  name: 'blog-module',
  configKey: 'blog-module',
  setup (options: any, nuxt: Nuxt) {

    // Auto register components
    nuxt.hook('components:dirs', (dirs) => {
      dirs.push({
        path: join(__dirname, 'components')
      })
    })

    // Auto register composables
    nuxt.hook('autoImports:dirs', (dirs) => {
      dirs.push(resolve(__dirname, './composables'))
    })

    // Auto register pages
    nuxt.hook('pages:extend', (pages) => {
      pages.push({
        name: 'blog-page',
        path: '/blog/:id',
        file: resolve(__dirname, './pages/blog/[id].vue')
      })
    })

    // Pinia store modules are auto imported
  }
})


Enter fullscreen mode Exit fullscreen mode

让我们逐步讨论一下:

  1. 我们正在定义一个新的 Nuxt 模块。
  2. blog-module为了更容易识别和区分,我们给它起了个名字。
  3. 我们给它一个配置键(在我们的例子中与名称相同),可用于将一些选项nuxt.config.ts直接传递到模块。
  4. 在设置函数中,我们定义了模块注册时应该发生什么。
  5. 在我们的例子中,我们希望自动注册组件、可组合项、页面和存储,以便我们可以在整个应用程序中使用

现在最后一步是将我们新创建的模块添加到以下modules部分nuxt.config.ts



// nuxt.config.ts

import { defineNuxtConfig } from 'nuxt'

// https://v3.nuxtjs.org/docs/directory-structure/nuxt.config
export default defineNuxtConfig({
  modules: [
    '@pinia/nuxt',
    '~/modules/blog/module'
  ]
})


Enter fullscreen mode Exit fullscreen mode

我们将其添加到以便modules所有诸如组件或可组合项之类的东西都可以自动导入。

就这样!我们现在有一个包含与博客相关的所有功能的模块。

概括

您已成功创建了一个包含自身组件、可组合项、页面和存储的博客模块。干得好!这里涵盖了很多知识,这应该是一个良好的开端,让您能够更轻松地构建下一个(Nuxt ;))项目。请务必尝试这种方法,因为可能会有更多有趣的功能可以用这种方式封装 :)

奖金

文章来源:https://dev.to/jacobandrewsky/using-modules-and-pinia-to-struct-nuxt-3-app-5963
PREV
什么是 DevRel?您今天如何开始使用它?
NEXT
利用 Nuxt.js 中的缓存 利用 Nuxt.js 中的缓存