在 Nuxt 中编写全局函数

Nuxt 中,我们可以通过以下三种方式创建 "插件" 或全局函数:

  1. 注入 Vue 实例(客户端)

    // plugins/<function-name>.js
    import Vue from 'vue'
    Vue.prototype.$<function-name> = () => {
      // 函数实现...
    }
  2. 注入 Nuxt 上下文(服务端)

    // plugins/<function-name>.js
    export default (context, inject) => {
      context.app.$<function-name> = () => {
        // 函数实现...
      }
    }
  3. 同时注入 Vue 实例和 Nuxt 上下文

    // plugins/<function-name>.js
    export default (context, inject) => {
      inject('<function-name>', () => {
        // 函数实现...
      })
    }

通过这些标准化格式,您可以轻松为应用创建全局函数。在接下来的章节中,我们将通过具体示例带您逐步实践。现在让我们开始吧!

将函数注入到 Vue 实例中

在这个示例中,我们将创建一个实现两数相加的函数(例如 1+2=3),并将其注入 Vue 实例。具体步骤如下:

  1. /plugins/ 目录下创建一个 .js 文件,导入 Vue,并将函数附加到 Vue.prototype 上:

    // plugins/vue-injections/sum.js
    import Vue from 'vue'
    Vue.prototype.$sum = (x, y) => x + y
  2. 配置 Nuxt 插件 在配置文件中注册插件路径:

    // nuxt.config.js
    export default {
      plugins: ['~/plugins/vue-injections/sum']
    }
  3. 全局调用函数 可在任意页面中使用该函数,例如:

    // pages/vue-injections.vue
    <template>
      <p>{{ this.$sum(1, 2) }}</p>
      <p>{{ sum }}</p>
    </template>
    
    <script>
    export default {
      data() {
        return {
          sum: this.$sum(2, 3)
        }
      }
    }
    </script>
  4. 在浏览器中运行该页面,你应该在屏幕上看到以下输出(即使你刷新页面):

    3
    5

将函数注入到 Nuxt 上下文中

在这个示例中,我们将创建一个计算数值平方的函数(例如 5*5=25),并通过以下步骤将其注入 Nuxt 上下文:

  1. 创建插件文件 在 /plugins/ 目录下创建 .js 文件,将函数挂载至 context.app:

    // plugins/ctx-injections/square.js
    export default ({ app }, inject) => {
      app.$square = (x) => x * x
    }
  2. 配置 Nuxt 插件 在配置文件中注册插件路径:

    // nuxt.config.js
    export default {
      plugins: ['~/plugins/ctx-injections/square']
    }
  3. 在页面中使用函数 可在任意能访问上下文的场景调用,例如 asyncData 方法:

    // pages/ctx-injections.vue
    <template>
      <p>{{ square }}</p>
    </template>
    
    <script>
    export default {
      asyncData(context) {
        return {
          square: context.app.$square(5)
        }
      }
    }
    </script>
  4. 运行效果 页面刷新后将始终显示:

    25

重要说明:

  • asyncData 方法在页面组件初始化前执行,因此:

    • 无法通过 this 访问 Vue 实例

    • 不能调用注入 Vue 实例的函数(如前例的 $sum 函数)

  • 上下文注入的函数(如本示例的 $square)同样不能在 Vue 生命周期钩子/方法(如 mountedupdated 等)中使用

如果需要创建同时支持 this 和上下文调用的函数,我们将在下一节演示如何将其同时注入 Vue 实例和 Nuxt 上下文。

将函数同时注入到 Vue 实例和 Nuxt 上下文中

在本示例中,我们将创建一个计算两数乘积的函数(如 2*3=6),并通过以下步骤同时注入 Vue 实例和 Nuxt 上下文:

  1. 创建插件文件 使用 inject 方法封装函数(自动添加 $ 前缀):

    // plugins/combined-injections/multiply.js
    export default ({ app }, inject) => {
      inject('multiply', (x, y) => x * y)
    }

    请注意,$ 会自动添加到你的函数名前面,所以你无需担心自己添加。

  2. 配置 Nuxt 插件

    // nuxt.config.js
    export default {
      plugins: ['~/plugins/combined-injections/multiply']
    }
  3. 在你拥有 contextthisVue 实例)访问权限的任何页面上使用该函数,例如:

    // pages/combined-injections.vue
    <template>
      <p>{{ this.$multiply(4, 3) }}</p>
      <p>{{ multiply }}</p>
      <button @click="updateStore">更新Store</button>
    </template>
    
    <script>
    export default {
      asyncData(context) {
        return { multiply: context.app.$multiply(2, 3) }
      },
      methods: {
        updateStore() {
          this.$store.dispatch('setNumbers')
        }
      }
    }
    </script>
  4. 运行效果 页面加载显示:

    12
    6

    该函数可在 Vue 生命周期的任意钩子中使用,例如:

    mounted() {
      console.log(this.$multiply(5, 3)) // 浏览器控制台将输出15
    }

    此外,该函数还可通过 thisVuex Store 的以下场景调用:

    • actions 对象中的方法

    • mutations 属性中的方法

    我们将在第 10 章《集成 Vuex 状态管理》中详细探讨 Vuex 的相关用法。

  5. 创建一个 .js 文件并将以下函数封装在 actionsmutations 对象中:

    // store/index.js
    export const state = () => ({
      xNumber: 1,
      yNumber: 3
    })
    
    export const mutations = {
      changeNumbers(state, newValue) {
        state.xNumber = this.multiply(3, 8)
        state.yNumber = newValue
      }
    }
    
    export const actions = {
      setNumbers({ commit }) {
        const newValue = this.multiply(9, 6)
        commit('changeNumbers', newValue)
      }
    }
  6. 在任何你喜欢的页面上使用上述 storeaction 方法(即 setNumbers),例如以下示例:

    <template>
      <div>
        <p>{{ $store.state }}</p>
        <button class="button" @click="updateStore">
          Update Store
        </button>
      </div>
    </template>
    
    <script>
    export default {
      methods: {
        updateStore() {
          this.$store.dispatch('setNumbers')
        }
      }
    }
    </script>
  7. 在浏览器中运行该页面,屏幕上应该显示以下输出(即使刷新页面也会保持):

    { "xNumber": 1, "yNumber": 3 }
  8. 点击 "更新 Store" 按钮后,带有上述数字的 store 默认状态将更改为:

    { "xNumber": 24, "yNumber": 54 }

这很棒。通过这种方式,我们可以编写一个在客户端和服务器端都能工作的插件。但有时我们需要专门在服务器端或客户端单独使用的函数。要做到这一点,我们必须指示 Nuxt 如何专门运行我们的函数。所以在下一节中,让我们了解如何实现这一点。

注入仅客户端或仅服务器的插件

在这个示例中,我们将创建一个用于两数相除的函数(例如 8 / 2 = 4),以及另一个用于两数相减的函数(例如 8 - 2 = 6)。我们将第一个函数注入 Vue 实例并限定其仅在客户端使用,而第二个函数则注入 Nuxt 上下文并限定其仅在服务端使用:

  1. 创建两个函数文件,分别添加 .client.js.server.js 后缀:

    // plugins/name-conventions/divide.client.js
    import Vue from 'vue'
    Vue.prototype.$divide = (x, y) => x / y
    
    // plugins/name-conventions/subtract.server.js
    export default ({ app }, inject) => {
      inject('subtract', (x, y) => x - y)
    }

    .client.js 结尾的函数文件将仅在客户端运行,而以 .server.js 结尾的函数文件则仅在服务端运行。

  2. Nuxt 配置文件的 plugins 属性中添加这两个文件路径:

    // nuxt.config.js:
    export default {
      plugins: [
        '~/plugins/name-conventions/divide.client.js',
        '~/plugins/name-conventions/subtract.server.js'
      ]
    }
  3. 在任意页面中使用这些插件,例如:

    // pages/name-conventions.vue
    <p>{{ divide }}</p>
    <p>{{ subtract }}</p>
    
    export default {
      data () {
        let divide = ''
        if (process.client) {
          divide = this.$divide(8, 2)
        }
        return { divide }
      },
      asyncData (context) {
        let subtract = ''
        if (process.server) {
          subtract = context.app.$subtract(10, 4)
        }
        return { subtract }
      }
    }
  4. 在浏览器中运行该页面,屏幕上将显示以下输出:

    4
    6

请注意:当首次访问或刷新页面时会得到上述结果。但如果通过 <nuxt-link> 导航到该页面,则只会显示:

4

特别提醒:我们必须用 process.client 条件语句包裹 $divide 方法,因为这是仅限客户端的函数。如果移除该条件判断,浏览器将报服务端错误:

this.$divide is not a function

同理,$subtract 方法必须用 process.server 条件语句包裹。如果移除该条件判断,浏览器将报客户端错误:

this.$subtract is not a function

虽然每次使用时都包裹条件判断可能不够理想,但在那些仅客户端调用的 Vue 生命周期钩子/方法(如 mounted 钩子)中,可以安全地省略 process.client 条件判断:

mounted () {
  console.log(this.$divide(8, 2))
}

浏览器控制台将输出 4。以下表格展示了八个 Vue 生命周期钩子/方法,值得注意的是在 Nuxt 应用中只有其中两个会在两端都被调用:

客户端和服务端 仅客户端
  • beforeCreate()

  • created()

  • beforeMount()

  • mounted()

  • beforeUpdate()

  • updated()

  • beforeDestroy()

  • destroyed()

需要注意的是,我们在 VueNuxt 应用中使用的 data 方法与 asyncData 方法一样,会在客户端和服务端同时被调用。因此,对于专为客户端设计的 $divide 方法,您可以在 "仅客户端"(ClientOnly)的钩子列表中不加条件判断地使用它。而专为服务端设计的 $subtract 方法,则可以在 nuxtServerInit 操作中安全地不加条件判断使用,如下例所示:

export const actions = {
  nuxtServerInit ({ commit }, context) {
    console.log(context.app.$subtract(10, 4))
  }
}

当应用在服务端运行时(包括刷新任意页面时),您将获得输出结果 6。需要特别说明的是,Nuxt 上下文只能通过以下两种方法访问:nuxtServerInitasyncData。其中 nuxtServerInit 操作将上下文作为第二个参数访问,而 asyncData 方法将其作为第一个参数访问。我们将在第 10 章《添加 Vuex Store》中详细讲解 nuxtServerInit 操作,不过现在,在下一节中,我们将重点介绍在 nuxtServerInit 操作之后、但在 Vue 实例和插件初始化之前,以及在你刚刚学习过的 $rootNuxt 上下文注入函数之前被注入的 JavaScript 函数。这类函数被称为 Nuxt 模块,通过本章的学习,您将掌握编写这些模块的方法。让我们开始吧。