将 Socket.IO 与 Nuxt 集成
我们将要构建的 Nuxt 应用程序与我们在上一章中构建的非常相似,其中包含一个 /users/ 目录,该目录在 /pages/ 目录下包含以下 CRUD 页面,用于添加、更新、列出和删除用户:
users
├── index.vue
├── _slug.vue
├── add
│ └── index.vue
├── update
│ └── _slug.vue
└── delete
└── _slug.vue
您可以从上一章复制这些文件。此应用程序中唯一的主要更改和区别在于 <script> 块,我们将在其中通过监听来自 Socket.IO 服务器的发送事件来实时列出用户。为此,我们需要使用 Socket.IO 客户端,您在“添加和使用 Socket.IO 服务器和客户端”部分中通过简单的 HTML 页面学习了相关知识。因此,让我们看看如何将我们已经知道的内容实现到 Nuxt 应用程序中:
-
通过 npm 将 Socket.IO 客户端安装到您的 Nuxt 项目中:
$ npm i socket.io-client
-
在 Nuxt 配置文件中创建以下应用程序协议、主机名和跨域端口的变量,以便我们稍后可以重用它们:
// nuxt.config.js const protocol = 'http' const host = process.env.NODE_ENV === 'production' ? 'a-cooldomain-name.com' : 'localhost' const ports = { local: '8000', remote: '4000' } const remoteUrl = protocol + '://' + host + ':' + ports.remote + '/'
这些变量用于以下情况:
-
当 Nuxt 应用程序处于生产环境时(即,当您使用 npm run start 运行应用程序时),host 变量用于获取 a-cool-domainname.com 的值。否则,它只取 localhost 作为默认值。
-
ports 变量中的 local 键用于为 Nuxt 应用程序设置服务器端口,并设置为 8000。请记住,Nuxt 提供应用程序服务的默认端口是 3000。
-
ports 变量中的 remote 键用于告知 Nuxt 应用程序 API 所在的服务器端口,即 4000。
-
remoteUrl 变量用于将 API 与前面的变量连接起来。
-
-
将前面的变量应用于 Nuxt 配置文件中的 env 和 server 选项,如下所示:
// nuxt.config.js export default { env: { remoteUrl }, server: { port: ports.local, host: host } }
因此,通过此配置,当通过以下方法提供应用程序服务时,我们可以再次访问 remoteUrl 变量:
process.env.remoteUrl context.env.remoteUrl
此外,在此配置中,我们在 server 选项中将 Nuxt 应用程序的默认服务器端口更改为 8000。默认端口是 3000,而默认主机是 localhost。但是,您可能出于某种原因想要使用不同的端口。这就是我们在此处查看如何更改它们的原因。
如果您想了解更多关于服务器配置和其他选项(如超时和 https)的信息,请访问 https://nuxtjs.org/api/configuration-server。
如果您想了解更多关于 env 配置的信息,请访问 https://nuxtjs.org/api/configuration-envthe-env-property。
-
安装 Nuxt Axios 和 Proxy 模块,并在 Nuxt 配置文件中配置它们,如下所示:
// nuxt.config.js export default { modules: [ '@nuxtjs/axios' ], axios: { proxy: true }, proxy: { '/api/': { target: remoteUrl, pathRewrite: {'^/api/': ''} } } }
请注意,我们在 proxy 选项中重用了 remoteUrl 变量。因此,我们发出的每个以 /api/ 开头的 API 请求都将转换为 http://localhost:4000/api/。但是由于我们的 API 的路由中没有 /api/,我们在使用 pathRewrite 选项将请求发送到 API 之前,从请求 URL 中删除了 /api/ 部分。
-
在 /plugin/ 目录中创建一个插件,用于抽象 Socket.IO 客户端的实例,以便我们可以在任何地方重用它:
// plugins/socket.io.js import io from 'socket.io-client' const remoteUrl = process.env.remoteUrl const socket = io(remoteUrl) export default socket
请注意,我们通过 process.env.remoteUrl 在 Socket.IO 客户端实例中重用了 remoteUrl 变量。这意味着 Socket.IO 客户端将调用 localhost:4000 上的 Socket.IO 服务器。
-
将 socket.io 客户端插件导入到 <script> 块中,并在 index 文件中使用 @nuxtjs/axios 模块获取用户列表。此 index 文件保存在 pages 下的 /users/ 目录中:
// pages/users/index.vue import socket from '~/plugins/socket.io' export default { async asyncData ({ error, $axios }) { try { let { data } = await $axios.get('/api/users') return { users: data.data } } catch (err) { // 处理错误。 } } }
-
在使用 asyncData 方法获取并设置用户之后,在 mounted 方法中使用 Socket.IO 插件监听 user.changefeeds 事件,以获取来自服务器的任何新的实时提要,如下所示:
// pages/users/index.vue export default { async asyncData ({ error, $axios }) { //... }, mounted () { socket.on('user.changefeeds', data => { if (data.new_val === undefined && data.old_val === undefined) { return } //... }) } }
在这里,您可以看到我们总是检查数据回调,以确保传入的提要中定义了 new_val 和 old_val。换句话说,我们希望确保在继续执行以下代码行之前,这两个键始终存在于提要中。
-
检查之后,如果我们收到 new_val 键中的数据但 old_val 键为空,则表示已向服务器添加了一个新用户。如果我们从服务器端收到新的提要,我们将使用 JavaScript 的 unshift 函数将新的用户数据添加到用户数组的顶部,如下所示:
// pages/users/index.vue mounted () { //... if(data.old_val === null && data.new_val !== null) { this.users.unshift(data.new_val) } }
然后,如果我们收到 old_val 键中的数据但 new_val 键为空,则表示已从服务器删除现有用户。因此,要通过其索引(在数组中的位置)从数组中删除现有用户,我们可以使用 JavaScript 的 splice 函数。但首先,我们必须使用 JavaScript 的 map 函数按其 ID 查找用户的索引,如下所示:
// pages/users/index.vue mounted () { //... if(data.new_val === null && data.old_val !== null) { var id = data.old_val.id var index = this.users.map(el => { return el.id }).indexOf(id) this.users.splice(index, 1) } }
最后,如果我们同时收到 new_val 和 old_val 键中的数据,则表示当前用户已更新。因此,如果用户已更新,我们必须首先在数组中找到用户的索引,然后使用 JavaScript 的 splice 函数替换它,如下所示:
// pages/users/index.vue mounted () { //... if(data.new_val !== null && data.old_val !== null) { var id = data.new_val.id var index = this.users.findIndex(item => item.id === id) this.users.splice(index, 1, data.new_val) } }
请注意,我们使用 JavaScript 的 findIndex 函数作为 map 函数的另一种替代方案。
如果您想了解更多关于我们在此用于操作 JavaScript 数组的 JavaScript 标准内置函数的信息,请访问以下链接:
-
unshift 函数:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift
-
splice 函数:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
-
map 函数:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
-
findIndex 函数:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex
-
-
将以下模板添加到 <template> 块以显示用户,如下所示:
// pages/users/index.vue <div> <h1>Users</h1> <ul> <li v-for="user in users" v-bind:key="user.uuid"> <nuxt-link :to="'/users/' + user.slug'"> {{ user.name }} </nuxt-link> </li> </ul> <nuxt-link to="/users/add"> Add New </nuxt-link> </div>
在此模板中,您可以看到我们只是使用 v-for 循环从 asyncData 方法获取的用户数据,并将用户 uuid 绑定到每个循环的元素。之后,mounted 方法中发生的任何实时提要都将响应式地更新用户数据和模板。
-
使用
npm run dev
运行Nuxt
应用程序。您应该在终端上看到以下信息:Listening on: http://localhost:8000/
-
并排打开浏览器上的两个选项卡,或者并排打开两个不同的浏览器,并将它们指向 localhost:8000/users。从其中一个选项卡(或浏览器)在 localhost:8000/users/add 添加一个新用户。您应该看到新添加的用户立即且同时实时地显示在所有选项卡(或浏览器)上,而无需您刷新它们。
您可以在本书的 GitHub 存储库中的 /chapter-17/frontend/ 和 /chapter-17/backend/ 中找到本章的所有代码和应用程序。
做得好——您成功了!我们希望您觉得这个应用程序有趣且简单,并且它能激励您进一步探索您目前所学到的知识。让我们总结一下我们在本章中学到的内容。