开发 Nuxt SPA
在开发 Nuxt SPA
时,需要记住一个重要的事项:提供给 asyncData
和 fetch
方法的 Nuxt
上下文将丢失它们的 req
和 res
对象,因为这些对象是 Node.js HTTP
对象。在本节中,我们将创建一个简单的用户登录身份验证,你应该已经熟悉了。但是,这次我们将使用 Nuxt SPA
来实现它。我们还将创建一个使用动态路由列出用户的页面,正如我们在第 4 章 “添加视图、路由和过渡” 中所学到的那样。让我们开始吧:
-
准备以下
.vue
文件,或者直接从上一章复制:-| pages/ ---| index.vue ---| about.vue ---| login.vue ---| secret.vue ---| users/ -----| index.vue -----| _id.vue
-
准备带有
store state
、mutations
、actions
和处理用户登录身份验证的index
文件的Vuex store
,如下所示:
-| store/
---| index.js
---| state.js
---| mutations.js
---| actions.js
正如我们在上一章中提到的,当我们静态生成 Nuxt
通用 SSR
应用程序时,我们将丢失 store
中的 nuxtServerInit
action,这在 Nuxt SPA
中也是一样的——我们在客户端将没有这个服务器 action
。因此,我们需要一个客户端的 nuxtServerInit
action 来模拟服务器端的 nuxtServerInit
action。我们将在下一节学习如何做到这一点。
创建客户端的 nuxtServerInit action
这些文件中的方法和属性与我们在过去的练习中使用的相同,除了 nuxtServerInit
action:
// store/index.js
const cookie = process.server ? require('cookie') : undefined
export const actions = {
nuxtServerInit ({ commit }, { req }) {
if (
req
&& req.headers
&& req.headers.cookie
&& req.headers.cookie.indexOf('auth') > -1
) {
let auth = cookie.parse(req.headers.cookie)['auth']
commit('setAuth', JSON.parse(auth))
}
}
}
由于 nuxtServerInit
仅由 Nuxt
从服务器端调用,因此 Nuxt SPA
中不涉及服务器。因此,我们需要一个解决方案。我们可以使用 Node.js 的 js-cookie
模块在用户登录时将身份验证数据存储在客户端,这使其成为替换服务器端 cookie
的最佳选择。让我们学习如何实现这一点:
-
通过
npm
安装 Node.js 的js-cookie
模块:$ npm i js-cookie
-
在
store
actions 中创建一个名为nuxtClientInit
的自定义方法(如果愿意,你可以选择任何你喜欢的名称),以检索cookie
中的用户数据。然后,当用户刷新浏览器时,将其设置回指定情况下的所需状态:// store/index.js import cookies from 'js-cookie' export const actions = { nuxtClientInit ({ commit }, ctx) { let auth = cookies.get('auth') if (auth) { commit('setAuth', JSON.parse(auth)) } } }
你可能还记得,刷新页面时,
store
的nuxtServerInit
action 始终在服务器端调用。此nuxtClientInit
方法也会发生同样的情况;每次刷新页面时,都应该在客户端调用它。但是,它不会自动调用,因此我们可以使用一个插件在Vue
根实例初始化之前每次都调用它。 -
在
/plugins/
目录中创建一个名为nuxt-client-init.js
的插件,该插件将通过store
中的dispatch
方法调用nuxtClientInit
方法:// plugins/nuxt-client-init.js export default async (ctx) => { await ctx.store.dispatch('nuxtClientInit', ctx) }
请记住,我们可以在
Vue
根实例初始化之前在插件中访问Nuxt
上下文。store
被添加到Nuxt
上下文中,因此,我们可以访问store
actions,而我们在这里感兴趣的是nuxtClientInit
方法。 -
现在,将此插件添加到
Nuxt
配置文件中以安装该插件:// nuxt.config.js export default { plugins: [ { src: '~/plugins/nuxt-client-init.js', mode: 'client' } ] }
现在,每次你刷新浏览器时,nuxtClientInit
方法将被调用,并且在 Vue
根实例初始化之前,状态将由此方法重新填充。正如你所见,当我们失去作为通用 JavaScript
应用程序的 Nuxt
的全部功能时,模仿 nuxtServerInit
action 并非易事。但是,如果你必须使用 Nuxt SPA
,那么可以使用我们刚刚创建的 nuxtClientInit
方法来解决此问题。
接下来,我们将使用 Nuxt
插件创建一些自定义 Axios
实例。这应该是你已经非常熟悉的内容。但是,能够创建自定义 Axios
实例很有用,因为即使我们也有 Nuxt Axios
模块,你也可以在需要时始终回退到 Axios
的原始版本。所以,让我们继续!
使用插件创建多个自定义 Axios 实例
在这个 spa
模式的练习中,我们需要两个 Axios
实例来向以下地址发起 API 调用:
-
localhost:4000 用于用户身份验证
-
jsonplaceholder.typicode.com 用于获取用户
我们将使用原始的 Axios
(https://github.com/axios/axios),因为它使我们能够灵活地创建具有一些自定义配置的多个 Axios
实例。让我们开始吧:
-
通过
npm
安装原始Axios
:$ npm i axios
-
在你需要的页面上创建一个
Axios
实例:// pages/users/index.vue const instance = axios.create({ baseURL: '<api-address>', timeout: <value>, headers: { '<x-custom-header>': '<value>' } })
但是直接在页面上创建 Axios
实例并不理想。理想情况下,我们应该能够提取这个实例并在任何地方重用它。通过 Nuxt
插件,我们可以创建提取的 Axios
实例。我们可以遵循两种方法来创建它们。我们将在下一节中介绍第一种方法。
在 Nuxt 配置文件中安装自定义 Axios 插件
在前面的章节中,你学习了如何使用 inject
方法创建一个插件,并通过 Nuxt
配置文件安装该插件。除了使用 inject
方法之外,值得了解的是,我们还可以直接将插件注入到 Nuxt
上下文中。让我们看看如何做到这一点:
-
在
/plugins/
目录中创建一个axios-typicode.js
文件,导入原始的axios
,并创建实例,如下所示:// plugins/axios-typicode.js import axios from 'axios' const instance = axios.create({ baseURL: 'https://jsonplaceholder.typicode.com' }) export default (ctx, inject) => { ctx.$axiosTypicode = instance inject('axiosTypicode', instance) }
正如你所见,在创建
axios
实例后,我们通过Nuxt
上下文 (ctx
) 注入插件,使用inject
方法,然后将其导出。 -
在
Nuxt
配置文件中安装此插件:// nuxt.config.js export default { plugins: [ { src: '~/plugins/axios-typicode.js', mode: 'client' } ] }
你必须将
mode
选项设置为client
,因为我们只需要在客户端使用它。 -
你可以从任何你喜欢的地方访问此插件。在本例中,我们想在用户索引页上使用此插件来获取用户列表:
// pages/users/index.vue export default { async asyncData({ $axiosTypicode }) { let { data } = await $axiosTypicode.get('/users') return { users: data } } }
在这个插件中,我们将自定义的
axios
实例直接注入到Nuxt
上下文 (ctx
) 中作为$axiosTypicode
,这样我们就可以使用JavaScript
的解构赋值语法将其解包为$axiosTypicode
并直接调用它。我们还使用inject
方法注入了插件,因此我们也使用ctx.app
调用此插件,如下所示:// pages/users/index.vue export default { async asyncData({ app }) { let { data } = await app.$axiosTypicode.get('/users') return { users: data } } }
创建一个自定义的 Axios
插件并不难,不是吗?如果你通过 Nuxt
配置文件安装插件,这意味着它是一个全局 JavaScript
函数,你可以从任何地方访问它。但是,如果你不想将其安装为全局插件,你可以跳过在 Nuxt
配置文件中安装它的步骤。这使我们进入了创建 Nuxt
插件的第二种方法。
手动导入自定义 Axios 插件
创建自定义 Axios
实例的另一种方法完全不涉及 Nuxt
配置文件。我们可以只将自定义实例导出为一个普通的 JavaScript
函数,然后在我们需要它的页面中直接导入它。让我们看看如何做到这一点:
-
在
/plugins/
目录中创建一个axios-api.js
文件,导入原始的axios
,并创建实例,如下所示:// plugins/axios-api.js import axios from 'axios' export default axios.create({ baseURL: 'http://localhost:4000', withCredentials: true })
正如你所见,我们不再使用
inject
方法;相反,我们直接导出该实例。 -
现在,我们可以在需要时手动导入它。在本例中,我们需要在
login
action 方法中使用它,如下所示:// store/actions.js import axios from '~/plugins/axios-api' async login({ commit }, { username, password }) { const { data } = await axios.post('/public/users/login', { username, password }) //... }
正如你所见,我们必须手动导入此插件,因为它没有插入到
Nuxt
生命周期中。 -
在
token
中间件中导入它,并在此axios
实例上设置Authorization
header,如下所示:// middleware/token.js import axios from '~/plugins/axios-api' export default async ({ store, error }) => { //... axios.defaults.headers.common['Authorization'] = `Bearer:${store.state.auth.token}` }
即使我们必须在使用此方法时手动导入插件,但至少我们将以下设置提取到了一个插件中,我们可以在任何需要的地方重用它:
{ baseURL: 'http://localhost:4000', withCredentials: true }
你可以在本书
GitHub
仓库的/chapter-15/frontend/
中找到Nuxt SPA
的代码以及这两种方法。
一旦你创建、测试和检查了所有代码和文件,你就可以部署 Nuxt SPA
了。让我们开始吧!