将 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 应用程序中:

  1. 通过 npm 将 Socket.IO 客户端安装到您的 Nuxt 项目中:

    $ npm i socket.io-client
  2. 在 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 与前面的变量连接起来。

  3. 将前面的变量应用于 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。

  4. 安装 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/ 部分。

  5. 在 /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 服务器。

  6. 将 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) {
                // 处理错误。
            }
        }
    }
  7. 在使用 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。换句话说,我们希望确保在继续执行以下代码行之前,这两个键始终存在于提要中。

  8. 检查之后,如果我们收到 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

  9. 将以下模板添加到 <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 方法中发生的任何实时提要都将响应式地更新用户数据和模板。

  10. 使用 npm run dev 运行 Nuxt 应用程序。您应该在终端上看到以下信息:

    Listening on: http://localhost:8000/
  11. 并排打开浏览器上的两个选项卡,或者并排打开两个不同的浏览器,并将它们指向 localhost:8000/users。从其中一个选项卡(或浏览器)在 localhost:8000/users/add 添加一个新用户。您应该看到新添加的用户立即且同时实时地显示在所有选项卡(或浏览器)上,而无需您刷新它们。

    您可以在本书的 GitHub 存储库中的 /chapter-17/frontend/ 和 /chapter-17/backend/ 中找到本章的所有代码和应用程序。

做得好——您成功了!我们希望您觉得这个应用程序有趣且简单,并且它能激励您进一步探索您目前所学到的知识。让我们总结一下我们在本章中学到的内容。