[{"data":1,"prerenderedAt":914},["ShallowReactive",2],{"content:\u002Fposts\u002F2025\u002Fhow-to-structure-a-large-scale-vue-application":3,"surround:\u002Fposts\u002F2025\u002Fhow-to-structure-a-large-scale-vue-application":903},{"id":4,"title":5,"body":6,"canonical":23,"categories":868,"date":870,"description":871,"draft":872,"extension":873,"image":874,"meta":875,"navigation":876,"path":877,"permalink":878,"readingTime":879,"recommend":884,"references":878,"seo":885,"sitemap":886,"stem":898,"tags":899,"type":878,"updated":870,"__hash__":902},"content\u002Fposts\u002F2025\u002Fhow-to-structure-a-large-scale-vue-application.md","【翻译】如何构建大型 Vue 应用程序",{"type":7,"value":8,"toc":848},"minimark",[9,33,36,42,49,56,59,64,67,70,76,79,82,85,89,92,95,98,168,171,174,177,180,183,202,214,225,228,237,242,245,252,257,265,270,278,281,290,293,299,313,318,321,324,329,332,427,435,439,442,446,449,463,466,487,492,495,498,543,547,554,559,562,614,617,620,623,642,647,652,659,662,669,672,694,697,700,707,712,725,731,751,760,763,766,786,814,822,825,840],[10,11,12,13,20,21,26,27,32],"p",{},"本文来自 ",[14,15,19],"a",{"href":16,"rel":17},"https:\u002F\u002Fvueschool.io\u002F",[18],"nofollow","Vue School","，这是你学习 vue 很好的站点。",[14,22,25],{"href":23,"rel":24},"https:\u002F\u002Fvueschool.io\u002Farticles\u002Fvuejs-tutorials\u002Fhow-to-structure-a-large-scale-vue-application\u002F",[18],"原文地址","，作者 ",[14,28,31],{"href":29,"rel":30},"https:\u002F\u002Fvueschool.io\u002Farticles\u002Fauthor\u002Fdaniel-kelly",[18],"Daniel Kelly"," 是 Vue School 的首席讲师，喜欢帮助其他开发人员充分发挥潜力。他拥有 10 多年使用 Vue.js、Nuxt.js 和 Laravel 等技术的开发经验。",[10,34,35],{},"如果您感觉翻译侵犯了您的著作权，可以联系我，我会在收到反馈后删除此文。",[37,38,39],"blockquote",{},[10,40,41],{},"有一部分链接是原文指向的链接，国内可能无法打开。这个还得您自己想办法喽🧐",[10,43,44],{},[45,46],"img",{"alt":47,"src":48},"","https:\u002F\u002Fbitmc.uno\u002Fpicgo\u002F68196a07f2dfc-78.webp",[50,51,52],"center",{},[53,54,55],"small",{},"原文头图",[57,58],"hr",{},[60,61,63],"h2",{"id":62},"什么是可预测的代码库","什么是可预测的代码库？",[10,65,66],{},"可预测性到底是什么意思？最简单的是，它能够直观地从功能请求或错误报告转到代码库中可以解决所述任务的位置。此外，它还能够快速了解​​您可以在代码库中的该位置访问哪些工具来完成手头的任务。",[10,68,69],{},"为什么这很重要？嗯，你可能和我一样，有过这样的经历：接手一个项目，或者被介绍到一个现有的项目，然后在第一个任务上，你可能会想：“我甚至不知道从哪里开始！”",[10,71,72],{},[45,73],{"alt":74,"src":75},"不知所措","https:\u002F\u002Fbitmc.uno\u002Fpicgo\u002F681973abbe11e-78.webp",[60,77,78],{"id":78},"可预测代码库的重要性",[10,80,81],{},"您甚至可能已经处理代码库一段时间并且有同样的想法！可预测的代码库可以尽可能地减轻这种体验，从而使开发人员更轻松地了解项目并提高持续工作的效率。",[10,83,84],{},"我认为这里值得注意的是，虽然可预测性是可能的，但没有一个项目是 100% 可预测的。每个项目，无论是新的还是现有的，都至少有一个轻微的学习曲线。另外，请注意可预测性并不意味着代码库或应用程序作为一个整体可以快速理解。许多大型应用程序过于复杂，无法实现这一点，并且需要时间才能完全掌握。因此，可预测性并不是要看到完整的拼图，而更像是了解某个拼图的形状并能够快速看到它适合的位置。事实上，良好的代码库的本质在于它可以一次理解一部分，而不应该要求其开发人员必须立即考虑整体。",[60,86,88],{"id":87},"可预测性的关键标准","可预测性的关键：标准",[10,90,91],{},"那么我们如何在代码库中实现可预测性呢？答案是：标准，简单明了。也许这不是您正在寻找的答案，但这是事实。让任何事情变得可预测的最好方法是让它遵循一套标准。例如，我可以几乎 100% 确定地预测我今天刚买的新全尺寸床单将适合我的床，即使我以前从未用过这些床单。为什么？因为床单的标准尺码系统。",[60,93,94],{"id":94},"社区范围内的可预测性标准",[10,96,97],{},"因此这就引出了一个问题，整个 Vue.js 社区存在什么样的标准？我想说有 4 个可用的标准来源。",[99,100,101,109,117,131],"ol",{},[102,103,104],"li",{},[14,105,108],{"href":106,"rel":107},"https:\u002F\u002Fvuejs.org\u002Fstyle-guide\u002F",[18],"Vue.js 风格指南",[102,110,111,116],{},[14,112,115],{"href":113,"rel":114},"https:\u002F\u002Fvueschool.io\u002Fcourses\u002Frapid-development-with-vite",[18],"Vite"," 生成的脚手架",[102,118,119,120,125,126,130],{},"官方 Vue.js 库（可以在 ",[14,121,124],{"href":122,"rel":123},"https:\u002F\u002Fvuejs.org\u002F",[18],"Vue.js 网站","的 ",[127,128,129],"code",{"code":129},"生态系统 -> 官方库"," 下面找到）",[102,132,133,134,139,140,139,145,139,150,155,156,161,162,167],{},"更宽泛地说，最流行的组件框架如 ",[14,135,138],{"href":136,"rel":137},"https:\u002F\u002Fvueschool.io\u002Fcourses\u002Fmaterial-ui-with-vuetify-and-vue-js",[18],"Vuetify"," 、 ",[14,141,144],{"href":142,"rel":143},"https:\u002F\u002Fquasar.dev\u002F",[18],"Quasar",[14,146,149],{"href":147,"rel":148},"https:\u002F\u002Fprimevue.org\u002F",[18],"PrimeVue",[14,151,154],{"href":152,"rel":153},"https:\u002F\u002Fwww.shadcn-vue.com\u002F",[18],"shad-cn vue"," 等，以及其他流行的社区工具如 ",[14,157,160],{"href":158,"rel":159},"https:\u002F\u002Fvueuse.org\u002F",[18],"VueUse"," 和 ",[14,163,166],{"href":164,"rel":165},"https:\u002F\u002Fnuxt.com\u002F",[18],"Nuxt","。",[10,169,170],{},"虽然其中一些比其他的更正式地被视为标准，但我认为它们都提供了在项目和开发人员之间建立一些共同模式的机会，从而产生更可预测的代码库。",[60,172,173],{"id":173},"官方库和组件库",[10,175,176],{},"我们先来谈谈官方库和流行组件库带来的标准化。虽然这些库的主要目的是提供功能，但其副作用是采用了共享标准。",[10,178,179],{},"例如，如果您使用 Vue Router，那么您不仅可以受益于包本身的功能，而且最终您可以在一个项目中实现路由，就像在另一个项目中实现路由一样，并且与世界各地的 Vue.js 开发人员实现路由的方式非常相似。",[10,181,182],{},"这似乎显而易见，但有一点需要说明。如果 Vue.js 中存在针对某个问题的流行或推荐解决方案（尤其是官方解决方案），我会在使用其他任何解决方案之前深思熟虑。我和其他人一样乐于 DIY 自己的组件、store 等，但通常情况下，从长远来看，使用经过验证的解决方案更有价值。这不仅是因为它们提供的功能、测试覆盖率和文档，还因为它们带来的标准化。",[10,184,185,186,191,192,139,195,198,199,167],{},"在选择使用这些更标准化的解决方案时，务必牢记您正在构建的是什么。您正在构建一个需要高度定制化和品牌化设计的网站吗？那么，也许流行的组件库并不适合您。或者，您可以从像 ",[14,187,190],{"href":188,"rel":189},"https:\u002F\u002Fwww.radix-vue.com\u002F",[18],"Radix Vue"," 这样的无样式组件库中免费获得相关功能，同时保留对设计的完全控制权。如果应用程序的外观可以更通用一些，那么完全没有必要自己构建所有内容。可以使用流行的 UI 库，例如 ",[14,193,138],{"href":136,"rel":194},[18],[14,196,149],{"href":147,"rel":197},[18]," 或 ",[14,200,144],{"href":142,"rel":201},[18],[203,204,206],"alert",{"type":205},"warning",[10,207,208,209],{},"Radix Vue 现在已更新为 ",[14,210,213],{"href":211,"rel":212},"https:\u002F\u002Freka-ui.com\u002F",[18],"Reka UI",[203,215,216,222],{},[217,218,219],"template",{"v-slot:title":47},[10,220,221],{},"译者说明",[10,223,224],{},"国内的话，还是考虑使用 Element Plus、 Ant Design Vue、Vant 等。中文资料很多，相关的问题也好排查。",[60,226,227],{"id":227},"标准文件结构的开始",[10,229,230,231,236],{},"当谈到项目标准时，文件结构是一个经常被谈论的话题，虽然 Vue 没有指定特定结构的文档，但它确实从使用 ",[14,232,235],{"href":233,"rel":234},"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcreate-vue",[18],"npm create vue@latest"," 生成的代码库中提供了一个良好的起点。",[10,238,239],{},[45,240],{"alt":47,"src":241},"https:\u002F\u002Fbitmc.uno\u002Fpicgo\u002F68196c4646f20-78.webp",[10,243,244],{},"我们大多数人可能都熟悉这种结构，这太棒了！这意味着我们离可预测性又近了一步！但是……其实并没有太多可以借鉴的地方。对于这种文件结构，你仍然需要做出很多决定。",[10,246,247,248,251],{},"那么，还有其他社区通用的标准吗？很高兴你问到这个问题！ ",[14,249,166],{"href":164,"rel":250},[18]," 在 Vue 社区非常流行，并且拥有更深入的文件结构模式。一个 Nuxt 项目的文件结构如下所示：",[10,253,254],{},[45,255],{"alt":47,"src":256},"https:\u002F\u002Fbitmc.uno\u002Fpicgo\u002F68196ca7a7789-78.webp",[10,258,259,264],{},[14,260,263],{"href":261,"rel":262},"https:\u002F\u002Fnuxt.com\u002Fdocs\u002Fguide\u002Fdirectory-structure\u002Fapp",[18],"这比 Nuxt 文档网站更全面，几乎每个文件夹和文件都有一整页的文档","。你还能要求更高的透明度和标准化吗？",[10,266,267],{},[45,268],{"alt":47,"src":269},"https:\u002F\u002Fbitmc.uno\u002Fpicgo\u002F68196ce554897-78.webp",[10,271,272,273,167],{},"此外，如果您热衷于领域驱动设计 (DDD)，您甚至可以利用 Nuxt 层来复制此文件结构，但根据您的业务需求将其拆分成独立的逻辑“存储桶”。 ",[14,274,277],{"href":275,"rel":276},"https:\u002F\u002Fvueschool.io\u002Farticles\u002Fvuejs-tutorials\u002Fdomain-driven-design-with-nuxt-layers\u002F",[18],"如果您有兴趣了解更多信息，我们有一篇专门讨论此主题的文章",[10,279,280],{},"有些人可能会想：“但我的 Vue.js 应用程序不需要服务端渲染，所以我其实不需要 Nuxt。” 然而，我认为没有理由不在不使用 Nuxt 的情况下启动任何新的 Vue.js 项目。不需要服务端渲染？没问题，直接关掉就行了。",[282,283,288],"pre",{"className":284,"code":286,"language":287,"meta":47},[285],"language-ts","export default defineNuxtConfig({\n    ssr: false\n})\n","ts",[127,289,286],{"__ignoreMap":47},[10,291,292],{},"甚至按路由进行配置（没错，获得 SSR 和纯 CSR 的完美组合！）",[282,294,297],{"className":295,"code":296,"language":287,"meta":47},[285],"export default defineNuxtConfig({\n    routeRules: {\n        \"\u002Fadmin\": { ssr: false },\n        \u002F\u002F all other routes are SSR 所有其他的路由都使用服务端渲染\n    }\n})\n",[127,298,296],{"__ignoreMap":47},[10,300,301,302,198,307,312],{},"重点在于，Nuxt 提供了大量详尽的文件结构标准，在我看来，放弃这些标准是不负责任的。（更不用说，这些文件结构还带有自动导入功能，让编码体验更加愉悦，而且如果你突然需要快速支持某些服务端 API 端点，Nuxt 也能帮到你）。如果你是 Vue 的专业人士，但刚接触 Nuxt，我推荐你学习 ",[14,303,306],{"href":304,"rel":305},"https:\u002F\u002Fmasteringnuxt.com\u002F",[18],"《精通 Nuxt》",[14,308,311],{"href":309,"rel":310},"https:\u002F\u002Fvueschool.io\u002Fcourses\u002Fnuxt-js-3-fundamentals",[18],"《Nuxt.js 3 基础课程》"," 。它其实就是在 Vue 之上叠加的丰富功能。帮自己一个忙，学习起来并不难。",[203,314,315],{},[10,316,317],{},"这几个课程都是 Vue School 中的课程，感兴趣可以去看看。",[10,319,320],{},"最后，我确实认为可以明智地进行一些补充，而且肯定必须这样做来处理其他用例，但我们稍后会详细讨论这些内容，因为它们在社区层面还没有标准化。",[60,322,323],{"id":323},"组件推荐规则",[10,325,326],{},[45,327],{"alt":47,"src":328},"https:\u002F\u002Fbitmc.uno\u002Fpicgo\u002F68197690509d9-78.webp",[10,330,331],{},"现在，重点关注组件的目录，Vue 样式指南为我们提供了一些进一步的建议，使我们的文件结构更加可预测。除其他外，样式指南在定义组件时鼓励以下内容：",[333,334,335,338,341,359,369,390,408],"ul",{},[102,336,337],{},"如果可能，每个组件应在其自己的专用文件 (SFC) 中定义",[102,339,340],{},"单文件组件应以 PascalCase 命名",[102,342,343,344,198,347,350,351],{},"基础组件应以相同的前缀开头（例如 ",[127,345,346],{"code":346},"Base",[127,348,349],{"code":349},"App","）\n",[333,352,353,356],{},[102,354,355],{},"您可以将基础组件视为应用程序范围内的可重用组件，例如按钮或模态框",[102,357,358],{},"这将它们组合在一起并声明它们的全局性、可重用性",[102,360,361,362,198,365,368],{},"组件名称应始终是多个单词构成的，以免与任何现有或未来的 HTML 元素发生冲突。不要创建 ",[127,363,364],{"code":364},"Table",[127,366,367],{"code":367},"Button"," 为名组件。",[102,370,371,372,375,376],{},"单实例组件应以前缀 ",[127,373,374],{"code":374},"The"," 开头\n",[333,377,378,387],{},[102,379,380,381,161,384],{},"例如网站 页眉(header) 或 页脚(footer)，它们应命名为 ",[127,382,383],{"code":383},"TheHeader",[127,385,386],{"code":386},"TheFooter",[102,388,389],{},"这将它们组合在一起并将它们声明为一次性使用",[102,391,392,393],{},"紧密耦合的子组件应以其父组件的名称作为前缀\n",[333,394,395,405],{},[102,396,397,398,401,402],{},"例如 ",[127,399,400],{"code":400},"TodoList"," 中的 ",[127,403,404],{"code":404},"TodoListItem",[102,406,407],{},"这将它们组合在一起并声明它们相关",[102,409,410,411],{},"组件名称应以最顶层（通常是通用）单词开头，以最具体的单词结尾\n",[333,412,413,424],{},[102,414,397,415,139,418,139,421],{},[127,416,417],{"code":417},"SearchWidgetInput",[127,419,420],{"code":420},"SearchWidgetResultsList",[127,422,423],{"code":423},"SearchWidget",[102,425,426],{},"这会将文件结构中的相关组件分组在一起",[10,428,429,430,434],{},"除此之外，",[14,431,433],{"href":106,"rel":432},[18],"完整的风格指南","还有许多其他标准，这些标准将帮助您的项目对社区范围内的开发人员来说更加可预测。我不会在这里一一列举它们，但强烈建议您自己阅读并遵循风格指南。",[60,436,438],{"id":437},"一些推荐的个人团队范围的可预测性标准","一些推荐的个人\u002F团队范围的可预测性标准",[10,440,441],{},"虽然官方消息来源为整个 Vue.js 社区制定了一些很好的标准，但还有其他一些模式并没有被广泛采用，根据我的经验，这些模式也同样有用，并且可以成为您或您团队项目的标准。这些标准是必要的，因为社区范围的标准并不是 100% 全面的，但在如何决定和维护团队标准时要小心并严格……如果你不这样做，它可能会成为不断变化的规则的兔子洞。这就是我对 Vue.js 项目标准的一些建议。",[60,443,445],{"id":444},"标准化路由页面命名约定","标准化路由\u002F页面命名约定",[10,447,448],{},"另一种有意义的做法是命名路由和页面组件的标准化方法。在典型的 CRUD 应用程序中，每个资源都有以下不同的页面：",[99,450,451,454,457,460],{},[102,452,453],{},"所有资源的列表",[102,455,456],{},"单个资源的视图",[102,458,459],{},"创建资源的表单",[102,461,462],{},"以及编辑资源的表单",[10,464,465],{},"虽然其中一些可能最终成为嵌套路由（例如从列表页面内查看模态叠加中的单个资源），但它们通常最终具有带有相应页面的专用路由。",[10,467,468,469,474,475,480,481,486],{},"由于我有 ",[14,470,473],{"href":471,"rel":472},"https:\u002F\u002Fvueschool.io\u002Fcourses\u002Flaravel-backends-for-vue-js-3",[18],"PHP Laravel框架"," 的使用经验，因此在命名路由并以可预测的方式定义其路径时，我本能地采用了 ",[14,476,479],{"href":477,"rel":478},"https:\u002F\u002Flaravel.com\u002F",[18],"Laravel"," 现有的标准。这使得我经验丰富的 Laravel 团队能够更快速、更直观地使用 Vue。以“照片”资源为例，我推荐的 ",[14,482,485],{"href":483,"rel":484},"https:\u002F\u002Flaravel.com\u002Fdocs\u002F11.x\u002Fcontrollers#actions-handled-by-resource-controllers",[18],"Laravel 命名约定"," ，以及适用于 Vue 的命名约定，在 Nuxt 项目中如下：",[10,488,489],{},[45,490],{"alt":47,"src":491},"https:\u002F\u002Fbitmc.uno\u002Fpicgo\u002F6819704d2b3bb-78.webp",[10,493,494],{},"Nuxt 项目中的页面文件结构示例。",[10,496,497],{},"让我们分析一下它的作用。",[333,499,500,511,525,535],{},[102,501,502,503,506,507,510],{},"C - ",[127,504,505],{"code":505},"create.vue"," 页面是您创建新照片的地方。（用户访问： ",[127,508,509],{"code":509},"\u002Fphotos\u002Fcreate"," ）",[102,512,513,514,517,518,521,522,510],{},"R - ",[127,515,516],{"code":516},"[id].vue"," 页面是您可以读取单个照片资源的页面。",[127,519,520],{"code":520},"index.vue"," 页面可以一次列出多张照片，方便用户嵌套查看他们感兴趣的照片。（用户访问 ",[127,523,524],{"code":524},"\u002Fphotos\u002F[whatever-the-id-is]",[102,526,527,528,531,532,510],{},"U - ",[127,529,530],{"code":530},"edit-[id].vue"," 页面是您更新照片资源的地方。（用户访问： ",[127,533,534],{"code":534},"\u002Fphotos\u002Fedit-[whatever-the-id-is]",[102,536,537,538,198,540,542],{},"D - 不需要专门的页面，因为它通常只是 ",[127,539,520],{"code":520},[127,541,516],{"code":516}," 页面上的弹出窗口",[60,544,546],{"id":545},"标准化-api-端点命名约定","标准化 API 端点命名约定",[10,548,549,550,553],{},"当然，这些只是视图。它们可能需要一些 API 端点来从数据库获取数据或将数据发送到数据库。如果您已经使用其他技术设置了后端，那就太好了！……希望它的端点能够按照一定的标准进行组织。如果您还没有设置后端，那么 Nuxt ",[127,551,552],{"code":552},"server\u002Fapi"," 目录中的以下结构会非常有效。",[10,555,556],{},[45,557],{"alt":47,"src":558},"https:\u002F\u002Fbitmc.uno\u002Fpicgo\u002F68197104a4cdb-78.webp",[10,560,561],{},"让我们也来分解一下它。",[333,563,564,575,594,604],{},[102,565,566,567,570,571,574],{},"C - 对 ",[127,568,569],{"code":569},"\u002Fapi\u002Fphotos\u002F"," POST 请求由 ",[127,572,573],{"code":573},"index.post.ts"," 处理，并在数据库中创建新照片",[102,576,577,578,581,582,585,586,589,590,593],{},"R - 对 ",[127,579,580],{"code":580},"\u002Fapi\u002Fphotos\u002F[whatever-the-id-is]"," 的 GET 请求由 ",[127,583,584],{"code":584},"[id].get.ts"," 处理，并返回该帖子的具体信息。对 ",[127,587,588],{"code":588},"\u002Fapi\u002Fphotos"," GET 请求也由 ",[127,591,592],{"code":592},"index.get.ts"," 处理，并返回照片的分页列表。",[102,595,596,597,599,600,603],{},"U - 对 ",[127,598,580],{"code":580}," 的 PUT 请求由 ",[127,601,602],{"code":602},"[id].put.ts"," 处理，并更新数据库中的照片数据",[102,605,606,607,609,610,613],{},"D - 对 ",[127,608,580],{"code":580}," 的 DELETE 请求由 ",[127,611,612],{"code":612},"[id].delete.ts"," 处理，并从数据库中删除（或软删除）该资源",[60,615,616],{"id":616},"一些更全面的文件结构标准",[10,618,619],{},"除了 Nuxt 提供的开箱即用的文件结构外，我还建议标准化一些目录。以下是我根据最近开发的 Vue\u002FNuxt 项目，对一些团队标准目录提出的建议。",[60,621,622],{"id":622},"将页面特定组件与页面组件放在一起",[10,624,625,626,629,630,633,634,637,638,641],{},"有时，你的组件可能只在单个页面或单个资源的几个页面中有用（例如上文中的 ",[127,627,628],{"code":628},"photos"," 资源）。在这种情况下，我发现将这些组件存储在 ",[127,631,632],{"code":632},"pages\u002F[resource (ie. photos)]"," 目录下的某个目录中会很有帮助，而不是放在 ",[127,635,636],{"code":636},"components"," 目录中。我喜欢把这个目录称为 ",[127,639,640],{"code":640},"partials"," 。它在文件系统中的样子如下：",[10,643,644],{},[45,645],{"alt":47,"src":646},"https:\u002F\u002Fbitmc.uno\u002Fpicgo\u002F681971b83413a-78.webp",[50,648,649],{},[53,650,651],{},"文件系统的屏幕截图，其中部分目录嵌套在页面\u002F照片中",[10,653,654,655,658],{},"这可以避免我在开发页面时在完全不同的目录之间来回切换。此外，它还能让我快速知道 ",[127,656,657],{"code":657},"PhotoForm.vue"," 仅在 4 个图片页面中的一个或多个中使用。",[10,660,661],{},"如果您担心 Nuxt 将这些部分转换为实际页面（实际上它确实会），您可以在构建时轻松删除它们。",[282,663,667],{"className":664,"code":665,"filename":666,"language":287,"meta":47},[285],"\u002F\u002F app\u002Frouter.options.ts\n\nimport type { RouterConfig } from \"@nuxt\u002Fschema\";\n\n\u002F\u002F https:\u002F\u002Frouter.vuejs.org\u002Fapi\u002Finterfaces\u002Frouteroptions.html\nexport default \u003CRouterConfig>{\n    routes: (_routes) => {\n        if (import.meta.env.MODE === \"production\") {\n            _routes = _routes.filter(route => !route.path.includes(\"partials\"));\n        }\n        return _routes;\n    },\n};\n","app\u002Frouter.options.ts",[127,668,665],{"__ignoreMap":47},[60,670,671],{"id":671},"在中心位置收集类型",[10,673,674,675,678,679,198,684,689,690,693],{},"我喜欢在所有 Vue\u002FNuxt 项目中包含的另一个标准目录是 ",[127,676,677],{"code":677},"@\u002Ftypes"," 目录。这是我定义项目所有类型的中心枢纽。即使这些类型是使用 ",[14,680,683],{"href":681,"rel":682},"https:\u002F\u002Fwww.prisma.io\u002F",[18],"Prisma",[14,685,688],{"href":686,"rel":687},"https:\u002F\u002Form.drizzle.team\u002F",[18],"Drizzle"," 之类的工具自动生成的，我也喜欢从这个自定义目录中导入和导出这些生成的类型。为什么？因为这意味着当我想要导入和使用类型时，我不必考虑文件位置。我写 ",[127,691,692],{"code":692},"import {} from \"@\u002Ftypes\""," ，然后就会得到一个包含所有类型（无论是自定义的还是生成的）的自动完成选项列表。",[60,695,696],{"id":696},"验证器文件夹",[10,698,699],{},"所有应用程序的一个共同需求是能够在用户输入进入数据库之前在后端验证用户输入，并且通常也在前端验证用户输入，这样用户就可以快速、立即获得他们需要修复的内容的反馈。",[10,701,702,703,706],{},"为了在我的项目中组织这些资源，我喜欢创建一个 ",[127,704,705],{"code":705},"@\u002Fvalidators"," 目录，然后为每种不同的资源类型创建一个文件。像这样：",[10,708,709],{},[45,710],{"alt":47,"src":711},"https:\u002F\u002Fbitmc.uno\u002Fpicgo\u002F68197261b9d80-78.webp",[10,713,714,715,718,719,724],{},"请注意，扩展名前的 ",[127,716,717],{"code":717},".validator"," 关键字并没有什么特别之处，它只是为了方便文件搜索而制定的命名规则。然后，在每个文件中，我将使用 ",[14,720,723],{"href":721,"rel":722},"https:\u002F\u002Fzod.dev\u002F",[18],"Zod"," 来定义各种请求类型的实际验证逻辑。如下所示：",[282,726,729],{"className":727,"code":728,"language":287,"meta":47},[285],"import { z } from \"zod\";\n\nconst BaseRules = z.object({\n    id: z.string().uuid(),\n    title: z.string().min(1),\n    content: z.string().min(1),\n});\n\nconst fields = Object.keys(BaseRules.shape) as [keyof typeof BaseRules.shape];\n\nexport const CreateRules = BaseRules.omit({ id: true });\n\nexport const UpdateRules = BaseRules;\n\nexport const DeleteRules = z.object({\n    id: z.string().uuid(),\n});\n\nexport const ReadRules = z.object({\n    id: z.string().uuid(),\n    include: z.enum(fields).optional(), \u002F\u002F for limiting the fields to return\n});\n\nexport const ListRules = z.object({\n    keywords: z.string().optional(), \u002F\u002F for searching\n    limit: z.number().int().positive().optional(), \u002F\u002F for pagination\n    offset: z.number().int().positive().optional(), \u002F\u002F for pagination\n    sortBy: z.enum(fields).optional(),\n    sortOder: z.enum([\"asc\", \"desc\"]).optional(),\n    include: z.enum(fields).optional(), \u002F\u002F for limiting the fields to return\n});\n",[127,730,728],{"__ignoreMap":47},[10,732,733,734,739,740,198,745,750],{},"现在，我可以同时在 API 端点和前端页面组件中导入这些不同的规则，以便在服务器端和客户端进行验证，同时只需声明一次验证器。我喜欢将 Zod 与 ",[14,735,738],{"href":736,"rel":737},"https:\u002F\u002Fformkit.com\u002F",[18],"Formkit"," 搭配使用来进行前端验证。您可以在我们的 ",[14,741,744],{"href":742,"rel":743},"https:\u002F\u002Fvueschool.io\u002Flessons\u002Fformkit-and-zod",[18],"FormKit 课程",[14,746,749],{"href":747,"rel":748},"https:\u002F\u002Fformkit.com\u002Fplugins\u002Fzod",[18],"FormKit Zod 插件","的文档中学习如何操作 。",[10,752,753,754,759],{},"或者，您可能想考虑使用 ",[14,755,758],{"href":756,"rel":757},"https:\u002F\u002Fvalibot.dev\u002F",[18],"Valibot"," 来处理这些验证。它本质上与 Zod 相同，但其模块化架构意味着它可进行树优化。这意味着您发送到浏览器进行前端验证的验证规则将仅包含您实际用于应用的规则。",[60,761,762],{"id":762},"可预测性总结",[10,764,765],{},"虽然您最好不要忽视一些社区范围的标准，但您也可以为您或您的团队制定许多标准，以使您的代码库更具可预测性。虽然上面提到的一些标准已被证明对我有用，但其他标准可能也适合您或您的团队。最重要的是在各个项目中坚持使用它们，以便它们能够实现其目的。",[10,767,768,769,161,774,779,780,785],{},"虽然可预测性标准对于您的大型 Vue.js 应用程序大有裨益，但您仍有许多工作要做。请务必设置代码检查和格式化工具，例如 ",[14,770,773],{"href":771,"rel":772},"https:\u002F\u002Fvueschool.io\u002Flessons\u002Fauto-linting-in-vs-code-eslint",[18],"ESLint",[14,775,778],{"href":776,"rel":777},"https:\u002F\u002Fvueschool.io\u002Flessons\u002Fauto-formatting-files-in-vs-code-volar-built-in-or-prettier",[18],"Prettier"," ，以保持代码整洁、无错误且一致。您可以通过提示在新的 Vue 项目中设置这些工具，也可以使用 ",[14,781,784],{"href":782,"rel":783},"https:\u002F\u002Fnuxt.com\u002Fmodules\u002Feslint",[18],"Nuxt 的 ESLint 模块"," 。",[203,787,788,793,805],{},[217,789,790],{"v-slot:title":47},[10,791,792],{},"译者注",[10,794,795,796,161,800,804],{},"点击可以访问 ",[14,797,773],{"href":798,"rel":799},"https:\u002F\u002Feslint.org\u002F",[18],[14,801,778],{"href":802,"rel":803},"https:\u002F\u002Fprettier.io\u002F",[18]," 官方地址，上面的链接是 Vue School 的课程地址。",[10,806,807,808,813],{},"你也可以使用",[14,809,812],{"href":810,"rel":811},"https:\u002F\u002Fgithub.com\u002Fantfu\u002Feslint-config",[18],"antfu 的 eslint 配置","，兼具代码格式校验和格式化且配置简单。我已经在很多商业项目中使用了。",[10,815,816,817,167],{},"最后，如果您想获得更多有关构建大型 Vue.js 应用程序的技巧，",[14,818,821],{"href":819,"rel":820},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=nhKCRYJQO_4",[18],"请查看我在 2023 年 Vue Conf US 上所做的演讲",[60,823,824],{"id":824},"译者结语",[10,826,827,828,833,834,839],{},"关于 Nuxt 的使用在国内还没有很好的课程，如果你可以访问 YouTube，你可以去订阅一下",[14,829,832],{"href":830,"rel":831},"https:\u002F\u002Fwww.youtube.com\u002F@TheAlexLichter",[18],"Alexander Lichter","的频道，这个频道专注于 Nuxt 和 Vue 生态有关的视频，对你使用 Nuxt 应该有很大的帮助。此频道的作者目前已加入尤大创建的 ",[14,835,838],{"href":836,"rel":837},"https:\u002F\u002Fvoidzero.dev\u002F",[18],"VoidZero"," 公司担任开发者关系一职。",[10,841,842,843,167],{},"如果你想看下如何从零搭建一个 Nuxt 项目，可以看下 Syntax 的 CJ 发布的 ",[14,844,847],{"href":845,"rel":846},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=DK93dqmJJYg&t=321s",[18],"Full Stack App Build | Travel Log w\u002F Nuxt, Vue, Better Auth, Drizzle, Tailwind, DaisyUI, MapLibre",{"title":47,"searchDepth":849,"depth":849,"links":850},4,[851,853,854,855,856,857,858,859,860,861,862,863,864,865,866,867],{"id":62,"depth":852,"text":63},2,{"id":78,"depth":852,"text":78},{"id":87,"depth":852,"text":88},{"id":94,"depth":852,"text":94},{"id":173,"depth":852,"text":173},{"id":227,"depth":852,"text":227},{"id":323,"depth":852,"text":323},{"id":437,"depth":852,"text":438},{"id":444,"depth":852,"text":445},{"id":545,"depth":852,"text":546},{"id":616,"depth":852,"text":616},{"id":622,"depth":852,"text":622},{"id":671,"depth":852,"text":671},{"id":696,"depth":852,"text":696},{"id":762,"depth":852,"text":762},{"id":824,"depth":852,"text":824},[869],"译","2025-05-06 09:40:22","构建 Vue.js 应用程序的最佳方法是什么，以便它可以随着规模的增长而扩展并保持可维护性和可扩展性？这是我在很多场合听到过的一个问题，我认为这个问题的答案在于可预测性原则。当谈到创建一个可扩展的项目时，您希望它的一切都尽可能可预测。",false,"md","https:\u002F\u002Fbitmc.uno\u002Fpicgo\u002F68198095a615b-78.webp",{},true,"\u002Fposts\u002F2025\u002Fhow-to-structure-a-large-scale-vue-application",null,{"text":880,"minutes":881,"time":882,"words":883},"24 min read",23.555,1413300,4711,1,{"title":5,"description":871},{"loc":877,"images":887},[888,889,890,891,892,893,894,895,896,897],{"loc":48},{"loc":75},{"loc":241},{"loc":256},{"loc":269},{"loc":328},{"loc":491},{"loc":558},{"loc":646},{"loc":711},"posts\u002F2025\u002Fhow-to-structure-a-large-scale-vue-application",[900,901,166],"翻译","Vue","7MuoA87Ovslv0GrQV-XPokva-bjtI6IVXzYeWVKHSDQ",[904,909],{"title":905,"path":906,"stem":907,"date":908,"type":878,"children":-1},"使用Intl.NumberFormat对数字进行格式化","\u002Fposts\u002F2025\u002Fintl-numberformat","posts\u002F2025\u002FIntl-NumberFormat","2025-04-30 16:15:29",{"title":910,"path":911,"stem":912,"date":913,"type":878,"children":-1},"【翻译】使用 Nuxt Layers 进行领域驱动设计","\u002Fposts\u002F2025\u002Fdomain-driven-design-with-nuxt-layers","posts\u002F2025\u002Fdomain-driven-design-with-nuxt-layers","2025-05-06 13:30:25",1789642585172]