深入理解 Nuxt.js 中 `middleware` (中间件) 的执行顺序和作用域。

观众朋友们,晚上好!欢迎来到今晚的 Nuxt.js 中间件小课堂。我是你们的老朋友,代码界的段子手,今天咱们就来聊聊 Nuxt.js 里那些神秘又重要的“中间人”—— middleware。

咱先打个招呼,今天可不是来听我吹牛的,咱要用通俗易懂的语言,加上一些实战代码,把 Nuxt.js 的中间件彻底搞明白。准备好了吗?系好安全带,发车!

什么是 Middleware?为啥要有它?

想象一下,你是一家夜店的保安。不对,是高级餐厅的领位员。客人来了,你不能啥也不管直接让人进去吧?你得看看人家有没有穿拖鞋,有没有预定,有没有带宠物… 这就是 Middleware 的作用!

在 Nuxt.js 里,Middleware 就像是请求到达页面之前的一道道关卡。它可以拦截请求,进行一些处理,比如:

  • 身份验证:检查用户是否已登录,没登录就踢回登录页。
  • 权限控制:检查用户是否有权限访问特定页面,没权限就显示 "403 Forbidden"。
  • 语言设置:根据用户的 Cookie 或浏览器设置,切换网站语言。
  • A/B 测试:根据用户 ID,将用户分配到不同的测试组,展示不同的页面版本。
  • 数据预取:在页面渲染之前,先从 API 获取一些数据。

总之,Middleware 让你可以在页面渲染之前,对请求进行各种各样的操作,保证用户看到的是最合适的内容。

Nuxt.js 中 Middleware 的种类

Nuxt.js 提供了三种类型的 Middleware:

  1. Route Middleware (路由中间件): 只在特定路由上运行。
  2. Named Middleware (命名中间件): 可在页面和布局中引用,具有可复用性。
  3. Global Middleware (全局中间件): 会在每个路由上运行。

咱们一个一个来扒。

1. Route Middleware (路由中间件)

这是最简单粗暴的一种。直接在 pages 目录下的 .vue 文件里定义。

// pages/secret.vue
<template>
  <h1>欢迎来到秘密基地!</h1>
</template>

<script>
export default {
  middleware: function ({ redirect }) {
    // 模拟用户未登录
    const isLoggedIn = false;

    if (!isLoggedIn) {
      return redirect('/login');
    }
  }
}
</script>

这段代码的意思是:访问 secret.vue 页面之前,先执行 middleware 函数。如果 isLoggedInfalse,就跳转到 /login 页面。

是不是很简单?

2. Named Middleware (命名中间件)

这种中间件更灵活。你需要先在 middleware 目录下创建一个 .js 文件,然后在页面或布局中引用它。

// middleware/auth.js
export default function ({ redirect, store }) {
  // 模拟用户未登录
  const isLoggedIn = store.state.auth.loggedIn; // 假设登录状态存储在 Vuex 中

  if (!isLoggedIn) {
    return redirect('/login');
  }
}

然后在 nuxt.config.js 文件中配置它,这样 Nuxt.js 才知道它的存在。

// nuxt.config.js
export default {
  router: {
    middleware: ['auth'] // 注册命名中间件
  },
  modules: [
    '@nuxtjs/axios',
    '@nuxtjs/auth-next' //如果用nuxt/auth-next 模块,可以不用自己写middleware
  ],
  auth: {
      strategies: {
        local: {
          token: {
            property: 'access_token',
            global: true,
            required: true,
            type: 'Bearer'
          },
          user: {
            property: false,
            autoFetch: true
          },
          endpoint: {
            login: { url: '/api/auth/login', method: 'post' },
            logout: { url: '/api/auth/logout', method: 'post' },
            user: { url: '/api/auth/user', method: 'get' }
          }
        }
      },
      redirect: {
        login: '/login',
        logout: '/',
        callback: '/login',
        home: '/'
      }
    }
}

最后,在页面或布局中引用它:

// pages/profile.vue
<template>
  <h1>欢迎来到个人中心!</h1>
</template>

<script>
export default {
  middleware: 'auth' // 引用命名中间件
}
</script>

或者,在布局中使用:

// layouts/default.vue
<template>
  <div>
    <nuxt />
  </div>
</template>

<script>
export default {
  middleware: 'auth' // 引用命名中间件
}
</script>

这样,所有使用 default 布局的页面都会先执行 auth 中间件。

3. Global Middleware (全局中间件)

全局中间件会在每个路由上运行,所以要谨慎使用。全局中间件在 middleware 目录下创建,文件名前面加上 .

// middleware/.log.js
export default function ({ route }) {
  console.log('访问了路由:', route.path);
}

Nuxt.js 会自动注册所有以 . 开头的中间件。

Middleware 的执行顺序

这可是个重点!Middleware 的执行顺序非常重要,它直接影响到你的代码逻辑。

  1. nuxt.config.js 中的 router.middleware 这是第一个执行的,按照数组顺序依次执行。
  2. 布局组件中的 middleware 按照布局的嵌套顺序,由外到内执行。
  3. 页面组件中的 middleware 这是最后执行的。

咱们用一个表格来总结一下:

执行顺序 类型 位置
1 全局中间件 middleware 目录下,以 . 开头的文件
2 nuxt.config.js router.middleware 数组
3 布局中间件 布局组件的 middleware 属性
4 页面中间件 页面组件的 middleware 属性

举个例子:

// nuxt.config.js
export default {
  router: {
    middleware: ['global-config']
  }
}

// middleware/.global-config.js
export default function ({ store }) {
  store.commit('setConfig', { theme: 'dark' });
}

// layouts/default.vue
<template>
  <div>
    <nuxt />
  </div>
</template>

<script>
export default {
  middleware: 'layout-auth'
}
</script>

// middleware/layout-auth.js
export default function ({ redirect, store }) {
  if (!store.state.auth.loggedIn) {
    return redirect('/login');
  }
}

// pages/index.vue
<template>
  <h1>首页</h1>
</template>

<script>
export default {
  middleware: 'page-data'
}
</script>

// middleware/page-data.js
export default async function ({ store }) {
  await store.dispatch('fetchData');
}

在这个例子中,中间件的执行顺序是:

  1. .global-config.js (全局中间件,设置主题)
  2. layout-auth.js (布局中间件,检查登录状态)
  3. page-data.js (页面中间件,获取数据)

Middleware 的作用域

Middleware 的作用域指的是它能访问哪些变量和函数。

  • context 对象: 这是最重要的对象,它包含了请求的所有信息,比如 route (路由信息), redirect (重定向函数), store (Vuex store), req (Node.js request 对象), res (Node.js response 对象) 等等。
  • this 在 Route Middleware 中,this 指向的是 Vue 组件实例。但是在 Named Middleware 和 Global Middleware 中,thisundefined。所以,尽量避免在 Named Middleware 和 Global Middleware 中使用 this

Middleware 的一些高级用法

  • 使用 async/await Middleware 可以是异步的,你可以使用 async/await 来处理异步操作。

    // middleware/fetch-data.js
    export default async function ({ store }) {
      await store.dispatch('fetchData');
    }
  • 返回 Promise 如果你的 Middleware 没有使用 async/await,也可以返回一个 Promise

    // middleware/fetch-data.js
    export default function ({ store }) {
      return store.dispatch('fetchData');
    }
  • 使用 error 函数: 如果 Middleware 发生错误,可以使用 error 函数来显示错误页面。

    // middleware/error-handler.js
    export default function ({ error }) {
      error({ statusCode: 500, message: '服务器错误' });
    }
  • 跳过后续中间件: 如果你想跳过后续的中间件,可以使用 return false。但是不推荐这样做,因为它会使代码逻辑变得难以理解。最好还是通过条件判断来控制中间件的执行。

一些最佳实践

  • 保持 Middleware 简洁: Middleware 应该只做一些简单的逻辑判断和数据处理,不要在里面写太多的业务代码。
  • 避免在 Middleware 中修改 reqres 对象: 除非你有充分的理由,否则不要在 Middleware 中修改 reqres 对象。
  • 使用 async/await 处理异步操作: 这样可以使代码更清晰易懂。
  • 充分利用 context 对象: context 对象包含了请求的所有信息,可以帮助你完成各种各样的任务。
  • 命名清晰的 Middleware: 命名清晰的 Middleware 可以提高代码的可读性。
  • 注释你的 Middleware: 注释可以帮助你和其他人理解 Middleware 的作用。

一个更复杂的例子

咱们来一个更复杂的例子,模拟一个用户登录状态验证,以及权限判断的场景。

// store/auth.js
export const state = () => ({
  loggedIn: false,
  user: null,
  permissions: []
})

export const mutations = {
  SET_USER (state, user) {
    state.user = user
  },
  SET_LOGGED_IN (state, loggedIn) {
    state.loggedIn = loggedIn
  },
  SET_PERMISSIONS (state, permissions) {
    state.permissions = permissions
  }
}

export const actions = {
  async fetchUser ({ commit }) {
    // 模拟从 API 获取用户信息
    await new Promise(resolve => setTimeout(resolve, 500)) // 模拟网络延迟
    const user = {
      id: 1,
      name: '张三',
      email: '[email protected]'
    }
    commit('SET_USER', user)
    commit('SET_LOGGED_IN', true)
    commit('SET_PERMISSIONS', ['read', 'write']) // 模拟用户权限
  },
  async logout ({ commit }) {
    // 模拟登出
    await new Promise(resolve => setTimeout(resolve, 500)) // 模拟网络延迟
    commit('SET_USER', null)
    commit('SET_LOGGED_IN', false)
    commit('SET_PERMISSIONS', [])
  }
}

// middleware/auth.js
export default function ({ redirect, store }) {
  if (!store.state.auth.loggedIn) {
    return redirect('/login')
  }
}

// middleware/permission.js
export default function ({ redirect, store, route }) {
  const requiredPermission = route.meta.permission // 从路由元数据中获取需要的权限
  if (requiredPermission && !store.state.auth.permissions.includes(requiredPermission)) {
    return redirect('/forbidden')
  }
}

// pages/admin/dashboard.vue
<template>
  <h1>管理后台</h1>
</template>

<script>
export default {
  middleware: ['auth', 'permission'],
  meta: {
    permission: 'admin' // 需要 admin 权限才能访问
  }
}
</script>

在这个例子中:

  • store/auth.js 定义了用户的登录状态、用户信息和权限。
  • middleware/auth.js 检查用户是否已登录,未登录就跳转到登录页。
  • middleware/permission.js 检查用户是否拥有访问该页面所需的权限,没有权限就跳转到 "403 Forbidden" 页面。
  • pages/admin/dashboard.vue 页面需要 admin 权限才能访问。

总结

Nuxt.js 的 Middleware 是一个非常强大的工具,可以让你在页面渲染之前对请求进行各种各样的操作。掌握 Middleware 的执行顺序和作用域,可以让你更好地控制你的应用程序的行为。

好了,今天的 Nuxt.js 中间件小课堂就到这里。希望大家有所收获!记住,代码不是死的,人是活的,灵活运用 Middleware,让你的 Nuxt.js 应用更加强大! 下课!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注