NUXT.JS学习笔记

1,053 阅读7分钟

看文档时一些记录,如有遗漏,后面更新^ ^

GETTING STARTED

一、视图

image.png

Nuxt.js 为页面提供的特殊配置项:

属性名描述
asyncData最重要的一个键, 支持 异步数据处理,另外该方法的第一个参数为当前页面组件的 上下文对象
fetchasyncData 方法类似,用于在渲染页面之前获取数据填充应用的状态树(store)。不同的是 fetch 方法不会设置组件的数据。详情请参考 关于 fetch 方法的文档
head配置当前页面的 Meta 标签, 详情参考 页面头部配置 API
layout指定当前页面使用的布局(layouts 根目录下的布局文件)。详情请参考 关于 布局 的文档
loading如果设置为false,则阻止页面自动调用this.$nuxt.$loading.finish()this.$nuxt.$loading.start(),您可以手动控制它,请看例子,仅适用于在 nuxt.config.js 中设置loading的情况下。请参考API 配置 loading 文档
transition指定页面切换的过渡动效, 详情请参考 页面过渡动效
scrollToTop布尔值,默认: false。 用于判定渲染页面前是否需要将当前页面滚动至顶部。这个配置用于 嵌套路由的应用场景。
validate校验方法用于校验 动态路由的参数。
middleware指定页面的中间件,中间件会在页面渲染之前被调用, 请参考 路由中间件

关于页面配置项的详细信息,请参考 页面 API

二、路由

Nuxt.js 依据 pages 目录结构自动生成 vue-router 模块的路由配置。

要在页面之间使用路由,我们建议使用<nuxt-link>标签,也可以用<NLink>标签

 <NuxtLink to="/about">
   About page
 </NuxtLink>

1.基础路由

假设 pages 的目录结构如下:

pages/
--| user/
-----| index.vue
-----| one.vue
--| index.vue

那么,Nuxt.js 自动生成的路由配置如下:

router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'user',
      path: '/user',
      component: 'pages/user/index.vue'
    },
    {
      name: 'user-one',
      path: '/user/one',
      component: 'pages/user/one.vue'
    }
  ]
}

2.动态路由

在 Nuxt.js 里面定义带参数的动态路由,需要创建对应的以下划线作为前缀的 Vue 文件 或 目录。

以下目录结构:

pages/
--| _slug/
-----| comments.vue
-----| index.vue
--| users/
-----| _id.vue
--| index.vue

Nuxt.js 生成对应的路由配置表为:

router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'users-id',
      path: '/users/:id?',
      component: 'pages/users/_id.vue'
    },
    {
      name: 'slug',
      path: '/:slug',
      component: 'pages/_slug/index.vue'
    },
    {
      name: 'slug-comments',
      path: '/:slug/comments',
      component: 'pages/_slug/comments.vue'
    }
  ]
}

你会发现名称为 users-id 的路由路径带有 :id? 参数,表示该路由是可选的。如果你想将它设置为必选的路由,需要在 users/_id 目录内创建一个 index.vue 文件。

:API Configuration generate

警告: generate 命令会忽略动态路由: API Configuration generate

3.路由参数校验

Nuxt.js 可以让你在动态路由组件中定义参数校验方法。

举个例子: pages/users/_id.vue

export default {
  validate({ params }) {
    // 必须是number类型
    return /^\d+$/.test(params.id)
  }
}

如果校验方法返回的值不为 truePromise中 resolve 解析为false或抛出 Error , Nuxt.js 将自动加载显示 404 错误页面或 500 错误页面。

4.嵌套路由

你可以通过 vue-router 的子路由创建 Nuxt.js 应用的嵌套路由。

创建内嵌子路由,你需要添加一个 Vue 文件,同时添加一个与该文件同名的目录用来存放子视图组件。

Warning: 别忘了在父组件(.vue文件) 内增加 <nuxt-child/> 用于显示子视图内容。!!

路径/users就会直接显示pages/users/index.vue的内容

users.vue

<template>
  <div>
    <nuxt-child to="" />
  </div>
</template>

假设文件结构如:

pages/
--| users/
-----| _id.vue
-----| index.vue
--| users.vue

Nuxt.js 自动生成的路由配置如下:

router: {
  routes: [
    {
      path: '/users',
      component: 'pages/users.vue',
      children: [
        {
          path: '',
          component: 'pages/users/index.vue',
          name: 'users'
        },
        {
          path: ':id',
          component: 'pages/users/_id.vue',
          name: 'users-id'
        }
      ]
    }
  ]
}

5.动态嵌套路由

不常用,链接:www.nuxtjs.cn/guide/routi…

6.未知嵌套深度的动态嵌套路由

如果您不知道 URL 结构的深度,您可以使用_.vue动态匹配嵌套路径。这将处理与更具体请求不匹配的情况。

文件结构:

pages/
--| people/
-----| _id.vue
-----| index.vue
--| _.vue
--| index.vue

将处理这样的请求:

PathFile
/index.vue
/peoplepeople/index.vue
/people/123people/_id.vue
/about_.vue
/about/careers_.vue
/about/careers/chicago_.vue

Note: 处理 404 页面,现在符合_.vue页面的逻辑。

7.命名视图

链接

三、异步数据

Nuxt.js 扩展了 Vue.js,增加了一个叫 asyncData 的方法,使得我们可以在设置组件的数据之前能异步获取或处理数据。

asyncData 方法

asyncData方法会在组件(限于页面组件)每次加载之前被调用。它可以在服务端或路由更新之前被调用。在这个方法被调用的时候,第一个参数被设定为当前页面的上下文对象,你可以利用 asyncData方法来获取数据,Nuxt.js 会将 asyncData 返回的数据融合组件 data 方法返回的数据一并返回给当前组件。

注意:由于asyncData方法是在组件 初始化前被调用的,所以在方法内是没有办法通过this来引用组件的实例对象

Nuxt.js 提供了几种不同的方法来使用 asyncData 方法,你可以选择自己熟悉的一种来用:

  1. 返回一个 Promise, nuxt.js 会等待该Promise被解析之后才会设置组件的数据,从而渲染组件.
  2. 使用asyncawait(后面笔记会补相关的内容)

强烈建议使用axios

如果您的项目中直接使用了node_modules中的axios,并且使用axios.interceptors添加拦截器对请求或响应数据进行了处理,确保使用 axios.create创建实例后再使用。否则多次刷新页面请求服务器,服务端渲染会重复添加拦截器,导致数据处理错误。

import axios from 'axios'
const myaxios = axios.create({
  // ...
})
myaxios.interceptors.response.use(
  function (response) {
    return response.data
  },
  function (error) {
    // ...
  }
)

拦截器:在请求或响应被thencatch处理前拦截它们

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });
​
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    return response;
  }, function (error) {
    // 对响应错误做点什么
    return Promise.reject(error);
  });

关于axios先挂个链接:axios,后面再写详细的笔记

返回 Promise

export default {
  asyncData() {
    return axios.get("https://jsonplaceholder.typicode.com/users").then((res) => {
      return { users: res.data };
    });
  },
};

使用 async 或 await

export default {
  async asyncData() {
    const { data } = await axios.get(
      "https://jsonplaceholder.typicode.com/users"
    );
    return { users: data };
  },
};

错误处理

Nuxt.js 在上下文对象context中提供了一个 error(params) 方法,你可以通过调用该方法来显示错误信息页面。params.statusCode 可用于指定服务端返回的请求状态码。

以返回 Promise 的方式举个例子:

export default {
  asyncData({ params, error }) {
    return axios
      .get(`https://my-api/posts/${params.id}`)
      .then(res => {
        return { title: res.data.title }
      })
      .catch(e => {
        error({ statusCode: 404, message: 'Post not found' })
      })
  }
}

四、中间件

中间件允许您定义一个自定义函数运行在一个页面或一组页面渲染之前。

每一个中间件应放置在 middleware/ 目录。文件名的名称将成为中间件名称 (middleware/auth.js将成为 auth 中间件)。

一个中间件接收 context 作为第一个参数:

export default function (context) {
  context.userAgent = process.server
    ? context.req.headers['user-agent']
    : navigator.userAgent
}

中间件执行流程顺序:

  1. nuxt.config.js
  2. 匹配布局
  3. 匹配页面

中间件可以异步执行,只需要返回一个 Promise 或使用第 2 个 callback 作为第一个参数:

middleware/stats.js

import axios from 'axios'export default function ({ route }) {
  return axios.post('http://my-stats-api.com', {
    url: route.fullPath
  })
}

然后在你的 nuxt.config.js 、 layouts 或者 pages 中使用中间件:

nuxt.config.js

module.exports = {
  router: {
    middleware: 'stats'
  }
}

现在,stats 中间件将在每个路由改变时被调用。

您也可以将 middleware 添加到指定的布局或者页面:

pages/index.vue 或者 layouts/default.vue

export default {
  middleware: 'stats'
}

五、Vuex 状态树

使用状态树

Nuxt.js 会尝试找到 src 目录(默认是应用根目录)下的 store 目录,如果该目录存在,它将做以下的事情:

  1. 引用 vuex 模块
  2. vuex 模块 加到 vendors 构建配置中去
  3. 设置 Vue 根实例的 store 配置项

Nuxt.js 支持两种使用 store 的方式,你可以择一使用:

  • 模块方式: store 目录下的每个 .js 文件会被转换成为状态树指定命名的子模块 (当然,index 是根模块)
  • Classic(不建议使用): store/index.js返回创建 Vuex.Store 实例的方法。

无论使用那种模式,您的state的值应该始终是function,为了避免返回引用类型,会导致多个实例相互影响。

普通方式

Nuxt.js 允许您拥有一个 store 目录,其中包含与模块对应的每个文件。

首先,只需将状态导出为 函数,将变量和操作作为 store/index.js 中的对象导出:

export const state = () => ({
  counter: 0
})
​
export const mutations = {
  increment(state) {
    state.counter++
  }
}

然后,您可以拥有 store/todos.js 文件:

export const state = () => ({
  list: []
})
​
export const mutations = {
  add(state, text) {
    state.list.push({
      text,
      done: false
    })
  },
  remove(state, { todo }) {
    state.list.splice(state.list.indexOf(todo), 1)
  },
  toggle(state, todo) {
    todo.done = !todo.done
  }
}

Vuex 将如下创建:

new Vuex.Store({
  state: () => ({
    counter: 0
  }),
  mutations: {
    increment(state) {
      state.counter++
    }
  },
  modules: {
    todos: {
      namespaced: true,
      state: () => ({
        list: []
      }),
      mutations: {
        add(state, { text }) {
          state.list.push({
            text,
            done: false
          })
        },
        remove(state, { todo }) {
          state.list.splice(state.list.indexOf(todo), 1)
        },
        toggle(state, { todo }) {
          todo.done = !todo.done
        }
      }
    }
  }
})

pages/todos.vue 中,使用 todos 模块:

<template>
  <ul>
    <li v-for="todo in todos">
      <input type="checkbox" :checked="todo.done" @change="toggle(todo)" />
      <span :class="{ done: todo.done }">{{ todo.text }}</span>
    </li>
    <li>
      <input placeholder="What needs to be done?" @keyup.enter="addTodo" />
    </li>
  </ul>
</template><script>
  import { mapMutations } from 'vuex'
​
  export default {
    computed: {
      todos() {
        return this.$store.state.todos.list
      }
    },
    methods: {
      addTodo(e) {
        this.$store.commit('todos/add', e.target.value)
        e.target.value = ''
      },
      ...mapMutations({
        toggle: 'todos/toggle'
      })
    }
  }
</script><style>
  .done {
    text-decoration: line-through;
  }
</style>

模块方法也适用于顶级定义,而无需在 store 目录中实现子目录

示例:您创建文件 store/state.js 并添加以下内容

export default () => ({
  counter: 0
})

相应的可以在文件夹中添加 store/mutations.js

export default {
  increment(state) {
    state.counter++
  }
}

模块文件

您可以将模块文件分解为单独的文件:state.js,actions.js,mutations.jsgetters.js。如果您使用index.js来维护state,getters,actionsmutations,同时具有单个单独的操作文件,那么仍然可以正确识别该文件。

注意:在使用拆分文件模块时,必须记住使用箭头函数功能this 在词法上可用。词法范围this意味着它总是指向引用箭头函数的所有者。如果未包含箭头函数,那么this将是未定义的(undefined)。解决方案是使用 "normal" 功能,该功能会将this指向自己的作用域,因此可以使用。

fetch 方法

fetch 方法会在渲染页面前被调用,作用是填充状态树 (store) 数据,与 asyncData 方法类似,不同的是它不会设置组件的数据。

  • 类型: Function

如果页面组件设置了fetch方法,它会在组件每次加载前被调用(在服务端或切换至目标路由之前)。

fetch方法的第一个参数是页面组件的上下文对象context,我们可以用fetch方法来获取数据填充应用得状态树。为了让获取过程可以异步,需要 返回一个Promise,Nuxt.js会等这个 promise完成后再渲染组件。

警告: 无法再内部使用this获取 组件实例,fetch是在 组件初始化之前被调用

例如 pages/index.vue

<template>
  <h1>Stars: {{ $store.state.stars }}</h1>
</template><script>
  export default {
    // 第一个参数是页面组件的上下文对象 context,这里是用了解构赋值
    fetch({ store, params }) {
      return axios.get('http://my-api/stars').then(res => {
        store.commit('setStars', res.data)
      })
    }
  }
</script>

你也可以使用 asyncawait 的模式简化代码如下:

<template>
  <h1>Stars: {{ $store.state.stars }}</h1>
</template><script>
  export default {
    async fetch({ store, params }) {
      let { data } = await axios.get('http://my-api/stars')
      store.commit('setStars', data)
    }
  }
</script>

Vuex

如果要在fetch中调用并操作store,请使用store.dispatch,但是要确保在内部使用async / await等待操作结束:

<script>
  export default {
    async fetch({ store, params }) {
      await store.dispatch('GET_STARS')
    }
  }
</script>

store/index.js

// ...
export const actions = {
  async GET_STARS({ commit }) {
    const { data } = await axios.get('http://my-api/stars')
    commit('SET_STARS', data)
  }
}

nuxtServerInit方法

如果在状态树中指定了 nuxtServerInit 方法,Nuxt.js 调用它的时候会将页面的上下文对象作为第 2 个参数传给它(服务端调用时才会酱紫哟)。当我们想将服务端的一些数据传到客户端时,这个方法是灰常好用的。

举个例子,假设我们服务端的会话状态树里可以通过 req.session.user 来访问当前登录的用户。将该登录用户信息传给客户端的状态树,我们只需更新 store/index.js 如下:

actions: {
  nuxtServerInit ({ commit }, { req }) {
    if (req.session.user) {
      commit('user', req.session.user)
    }
  }
}

如果你使用状态树模块化的模式,只有主模块(即 store/index.js)适用设置该方法(其他模块设置了也不会被调用)。

这时context被赋予nuxtServerInit作为第二个参数,它与asyncDatafetch方法相同。

nuxtServerInit 方法接收的上下文对象和 fetch 的一样,但不包括 context.redirect()context.error()

注意:异步nuxtServerInit操作必须返回 Promise 来通知nuxt服务器等待它们。

actions: {
  async nuxtServerInit({ dispatch }) {
    await dispatch('core/load')
  }
}