你的 Nuxt 应用总是和与之匹配的后端服务共同成长,慢慢发展壮大的。这时,你的 API 也从屈指可数直到变成了如广袤丛林般的庞大资源,而你依然想要在这“丛林”中称王。这意味着你必须合理地组织这些 API 以保证它们都是有迹可循的,而非一团乱麻。假设这样一个场景,你想要重命名一个资源,把它的名字从 images
改为 photos
,你绞尽脑汁,试图在茫茫代码中寻找出所有需要更改的点,并避免错误地改变其他不应该被涉及的变量。光是在大脑里想想就觉得很恶心了吧。或者再想象一下,当你需要为了鉴权 (Authorization) 在 http 请求头里添加另一个值的时候吧。
所以,在前端合理组织你的 API 调用是非常有必要的。但在 Nuxt 中应该怎么搞呢?
关于 Nuxt 的两面性
在 Nuxt 中,想要合理组织 API 请求会让人感觉很麻烦,因为你要同时兼顾客户端和服务端。你可能需要在以下的几种情境中取回数据:
- 在服务端的
asyncData
和fetch
方法中; - 在 Vuex store (action) 中;
- 在处于客户端环境下的 Vue 组件中。
这意味着,我们需要有一个能满足以上所有情境的解决方案。
关于 Nuxt axios 模块
如果你还没有使用 Nuxt 官方提供的 axios 模块 (@nuxtjs/axios[2]),你真的应该切换到这上面来了,现在!立刻!马上!然后,你就不需要再到处引入 axios 了。引入 Nuxt 官方的 axios 模块后,你可以在客户端的组件中通过 this.$axios
调用 axios,也可以在像 asyncData
和 fetch
这样的可以在服务端执行的方法中通过 Nuxt 的上下文调用 (ctx.$axios
),也可以在 Vuex 中通过 this.$axios
调用。并且远不止如此,你还可以设置默认的请求头,基于你的环境设置 baseURL
,以及设置一些其他你认为需要的选项。另外,它还为所有的 HTTP 请求类型提供了简短的变量 ($get
…) 来方便你的使用。
但这就够了吗?当然不!我们找到了一个集中配置 axios 的方案,但这仅仅解决了一部分麻烦。
(译者有话说:事实上,
nuxtjs/axios
模块除了可以同时侵入服务端和客户端的上下文中这一点,axios 本身也具备上文提及的别的优点,并且更加灵活易用。nuxtjs/axios
的定制一定程度上方便了使用,但也带来了很大的制约。)
抽象我们的 REST API
现在开始有趣的部分。在下文中,假设使用 RESTful 风格的 API。下文中涉及的技术适用于大部分 API (风格),但需要做一些细微的调整。
在我们的例子中,我们将使用 JSONPlaceholder API[3],因为它涵盖了所有的 HTTP 请求类型,且并不复杂。
让我们看一下帖子资源。有如下操作:
- 新建帖子
- 展示一个帖子的内容
- 在其首页展示帖子列表
- 更新帖子
- 删除帖子
再考虑一下其他的资源,比如用户,模式如下:
- 创建用户
- 展示某个用户详情
- 展示用户列表
- 更新用户
- 删除用户
基于以上,我们可以抽象我们的 API 为一个简单的 JS 对象,并在其中为每一个动作创建方法。创建一个类 (class) 是一个好的选择。
我们这里参考的设计模式为仓库模式 (Repository Pattern)。
让我们创建如下文件,~/api/repository.js
:
|
|
ok,我们已经有了一个基本的 API 抽象的框架,虽然我们还需要继续完善这些方法。这时,由 Nuxt axios 提供的 $axios
就派上了用场,但怎样才能把它注入到一个外部的模块中呢?直接通过 this.$axios
调用反正是不行了。
依赖注入来拯救我们
这时,另一个术语闪过脑海:依赖注入 (Dependency Injection)。在像 PHP 或 Java 这样的面向对象的语言中,这是经常用到的模式,当然,它也会在我们的应用场景中提供助力。
通常,如果你依赖一个像 axios 这样的工具库,你大概会这么做:
|
|
在多数场景下,这没问题。但在我们的场景下,这么做会有如下劣势:
- 当你需要的时候,你无法很容易地将这一实现替换为另一个;
- 在运行时之前,依赖需要已知
前者在我们的案例中并不是大问题,但是后者却无法忽视。虽然我们已知有 axios 作为依赖,但是我们并不能在实际的应用中引入它。
现在换个思路,我们将 axios 作为依赖注入。这意味着我们只是简单的把它作为一个参数传递进一个函数中(如果你使用的是类,则传入构造器中)。
|
|
Duang~依赖被注入了!让我们完善我们之前基于仓库模式的框架并愉快地将 $axios
用起来吧:
|
|
概括一下我们的实现
ok,一切顺利!目前为止,我们获取帖子资源的方法依然是写死的。为了增强复用性,我们希望动态传递各类资源(接口)的 URL。
我们不会直接在我们的默认导出方法中再次增加第二个参数。作为替代,我们将改写这个函数为一个高阶函数(说人话就是在一个函数中返回另一个函数):
|
|
现在让我们在某个地方引入这个函数,我们可以通过复用它来组织更多的资源获取的接口,同时仅仅需要单次引入 axios 实例即可:
|
|
基于这一模式,你不必再一遍又一遍传递配置项了。
借用 NuxtJs 的插件的力量
走到现在,我们已经成功地抽象出了 API resources,并找到了复用抽象的恰当方式。但依然有两个问题摆在我们面前:
- 我们如何使这一抽象在 Nuxt 应用中随处可用?
- 如何将来自 Nuxt 模块的 axios 实例注入?
我们将使用一个 Nuxt 插件解决这两个问题。Nuxt 的插件机制通常被用来添加全局 Vue 组件或者方法库,以及更多其他的用途。值得注意的是,插件们将在 Vue 根实例被创建之前执行。
ok,让我们创建一个插件 ~/plugins/repository.js
。
如果一个插件有一个默认导出,被导出的函数将获得两个参数,一个是Nuxt 的上下文(context),另一个是 inject
。
|
|
在这个函数内部,我们可以拿到所有需要的东西,从而让我们在整个 Nuxt 应用中都可以调用到 API 仓库,并成功将 axios 实例注入到我们的架构之中。
传入 axios 实例
因为在插件的默认导出方法中, context
唾手可得,所以传入 axios 实例的难题也就迎刃而解了:
|
|
注入它!
现在重点来了。为了让我们的以上实现在整个 Nuxt 应用中(组件中,asyncData
方法中以及更多的地方,参考上文)可用,要怎么办呢?当然不可能去手动执行了,我们将通过 inject
方法,也就是第二个参数来实现这一需求:
|
|
inject
方法接收一个 name(使用的时候需要在这个 key 前加一个 $
前缀)作为第一个参数,接收与这个 key 对应的值作为第二个参数,这个值可以是原始数据类型,对象或者函数。
inject
方法将做如下事情:
- 添加 kv 对到 Vue 的原型中,这样就可以在组件或 store 中通过
this.$key
调用了 - 添加 kv 对到
ctx.app
对象中,这样就可以在asycData | fetch
等方法以及其他地方使用了
使用它!
我们已经完成了所有的配置,唯一需要做的就是在实际开发中使用它了。
上下文环境中 this.$repositories
就可以获得对象
总结
通过 API 仓库的形式组织你的 api 调用,使它们统一到一起从而有迹可循,同时减少了一股脑的代码复制粘贴,并且能够更容易地进行 debug 和修改。
注入一个对象到上下文中,以及通过 Nuxt 插件注入其他依赖的思想,同样可以作为一个常用的模式应用到其他的使用场景中。期待举一反三。
原文地址: https://blog.lichter.io/posts/nuxt-api-call-organization-and-decoupling/