Nuxt.js 是一个基于 Vue.js 的服务端渲染应用框架,它可以帮我们轻松的实现同构应用。
通过对客户端/服务端基础架构的抽象组织,Nuxt.js 主要关注的是应用的 UI渲染。
我们的目标是创建一个灵活的应用框架,你可以基于它初始化新项目的基础结构代码,或者在已有 Node.js 项目中使用 Nuxt.js。
Nuxt.js 预设了利用 Vue.js 开发服务端渲染的应用所需要的各种配置。
除此之外,我们还提供了一种命令叫: nuxt generate
,为基于 Vue.js 的应用提供生成对应的静态站点的功能。
我们相信这个命令所提供的功能,是向开发集成各种微服务(Microservices)的 Web 应用迈开的新一 步。
作为框架,Nuxt.js 为 客户端/服务端
这种典型的应用架构模式提供了许多有用的特性,例如异步数据 加载、中间件支持、布局支持等非常实用的功能。
Nuxt提供两种方式创建项目:
初始化项目目录并安装nuxt
- # 创建示例项目
- mkdir nuxt-js-demo
- # 进入示例项目目录中
- cd nuxt-js-demo
- # 初始化 package.json 文件
- npm init -y
- # 安装 nuxt
- npm innstall nuxt
在package.json中scripts添加
- "scripts": {
- "dev": "nuxt"
- }
创建页面并启动项目
在根目录下创建pages目录,并创建index.vue文件
- <template>
- <div>
- <h1>hello nuxt.js</h1>
- </div>
- </template>
-
- <script>
- export default {
- name: 'HomePage'
- }
-
- </script>
- <style>
- </style>
启动项目
npm run dev
现在我们的应用运行在 http://localhost:3000 上
Nuxt中的基础路由
Nuxt会依据pages目录中的所有.vue文件生成应用的路由配置
假设 pages 的目录结构如下:
- pages/
- --| user/
- -----| index.vue
- -----| one.vue
- --| index.vue
那么,Nuxt自动生成路由配置如下
- 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'
- }
- ]
- }
Nuxt.js 依据 pages 目录结构自动生成 vue-router 模块的路由配置。
路由导航
<nuxt-link>
组件动态路由
在 Nuxt.js 里面定义带参数的动态路由,需要创建对应的以下划线作为前缀的 Vue 文件 或 目录。 以下目录结构:
- pages/
- --| _slug/
- -----| comments.vue
- -----| index.vue
- --| users/
- -----| _id.vue
- --| index.vue
Nuxt生成对应的路由配置表为:
- 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 文件。
嵌套路由
创建内嵌子路由,你需要添加一个 Vue 文件,同时添加一个与该文件同名的目录用来存放子视图组件。
需要在父组件中增加<nuxt-child/>
用于显示子视图内容
- 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'
- }
- ]
- }
- ]
- }
创建nuxt.config.js文件
- // nuxt配置模块
- module.exports = {
- router: {
- // http://localhost:3000/app/
- base: '/app/',
- // 扩展路由配置
- // routes 一个数组,路由配置表
- // resolve:解析路由组件路径
- extendRoutes (routes, resolve) {
- routes.push({
- // name: 'custom',
- // path: '*',
- // component: resolve(__dirname, 'pages/404.vue')
- path: '/hello',
- name: 'hello',
- component: resolve(__dirname, 'pages/about.vue')
- })
- }
- }
- }
视图
- <!DOCTYPE html>
- <html {{ HTML_ATTRS }}>
- <head {{ HEAD_ATTRS }}>
- {{ HEAD }}
- </head>
- <body {{ BODY_ATTRS }}>
- <!-- 渲染内容最终会注入到这里 -->
- <h1>app.html</h1>
- {{ APP }}
- </body>
- </html>
根目录下创建layouts目录,default.vue为默认布局组件
默认布局
- <template>
- <div>
- <h1>layouts/default组件</h1>
- <!-- 页面出口,类似于子路由出口 -->
- <nuxt />
- </div>
- </template>
-
- <script>
- export default {
- name: 'layoutsDefault'
- }
-
- </script>
- <style>
- </style>
自定义布局
新增foo.vue组件
- <template>
- <div>
- <h1>layouts/foo组件</h1>
- <!-- 页面出口,类似于子路由出口 -->
- <nuxt />
- </div>
- </template>
-
- <script>
- export default {
- name: 'layoutsFoo'
- }
-
- </script>
- <style>
- </style>
在pages中的路由中添加layout: 'foo'
- <template>
- <div>
- <h1>hello nuxt.js</h1>
- </div>
- </template>
-
- <script>
- export default {
- name: 'HomePage',
- layout: 'foo',
- // 默认 layout设置自定义的布局组件 未设置的路由还会走默认的布局组件
- // layout: 'default'
- }
-
- </script>
- <style>
- </style>
默认 Meta 标签
- // nuxt.config.js
- export default {
- head: {
- title: 'my website title',
- meta: [
- { charset: 'utf-8' },
- { name: 'viewport', content: 'width=device-width, initial-scale=1' },
- {
- hid: 'description',
- name: 'description',
- content: 'my website description'
- }
- ],
- link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
- }
- }
个性化特定页面的 Meta 标签
- // 指定页面pages/index.vue
- // 设置个性化meta标签 设置页面title和description对SEO非常有用
- head () {
- return {
- title: `${this.article.title} - RealWorld`,
- meta: [
- { hid: 'description', name: 'description', content: this.article.description }
- ]
- }
- }
Nuxt.js 扩展了 Vue.js,增加了一个叫* asyncData *的方法,使得我们可以在设置组件的数据之前能 异步获取或处理数据。
- // 当你想要动态页面内容有利于 SEO 或者是提升首屏渲染速度的时候,就在 asyncData 中发请求拿数据
- // 注意事项:1.只能在页面组件中使用,子组件中可通过props获取父组件异步数据 2.没有this,在组件初始化之前被调用
- // 调用时机:1.获取服务端渲染数据(确保异步数据在渲染到客户端之前已经填充渲染完成,提高渲染速度,有利于SEO) 2.客户端路由更新之前也会被调用
- async asyncData () {
- // 此行输出在服务端执行
- // 在浏览器也会输出 包裹在Nuxt SSR中
- console.log('async Data')
- const res = await axios({
- method: 'GET',
- url: 'http://localhost:3000/data.json'
- })
- // 返回的对象可以直接在页面组件使用
- // 在vue调试工具中有了posts和title
- // asyncData返回的数据会和data中的混合
- return res.data
- },
-
- // 如果是非异步数据或者普通数据,则正常的初始化到 data 中即可
- data () {
- return {
- }
- }
可通过 context 来了解该对象的所有属性和方法。
- async asyncData (context) {
- console.log(context)
- const { data } = await axios({
- method: 'GET',
- url: 'http://localhost:3000/data.json'
- })
- const id = Number.parseInt(context.params.id)
- return {
- article: data.posts.find(item => item.id === id)
- }
- }
context 变量的可用属性一览:
属性字段 | 类型 | 可用 | 描述 |
---|---|---|---|
app | Vue 根实例 | 客户端 & 服务端 | 包含所有插件的 Vue 根实例。例如:在使用 axios 的时候,你想获取 axios 来获取 |
isClient | Boolean | 客户端 & 服务端 | 是否来自客户端渲染(废弃。请使用 process.client ) |
isServer | Boolean | 客户端 & 服务端 | 是否来自服务端渲染(废弃。请使用 process.server ) |
isStatic | Boolean | 客户端 & 服务端 | 是否来自 nuxt generate 静态化(预渲染)(废弃。请使用 process.static ) |
isDev | Boolean | 客户端 & 服务端 | 是否是开发 dev 模式,在生产环境的数据缓存中用到 |
isHMR | Boolean | 客户端 & 服务端 | 是否是通过模块热替换 webpack hot module replacement (仅在客户端以 dev 模式) |
route | Vue Router 路由 | 客户端 & 服务端 | Vue Router 路由实例 |
store | Vuex 数据 | 客户端 & 服务端 | Vuex.Store 实例。只有vuex 数据流存在相关配置时可用 |
env | Object | 客户端 & 服务端 | nuxt.config.js 中配置的环境变量,见 环境变量 api |
params | Object | 客户端 & 服务端 | route.params 的别名 |
query | Object | 客户端 & 服务端 | route.query 的别名 |
req | http.Request | 服务端 | Node.js API 的 Request 对象。如果 Nuxt 以中间件形式使用的话,这个对象就根据你所使用的框架而定。nuxt generate 不可用 |
res | http.Response | 服务端 | Node.js API 的 Response 对象。如果 Nuxt 以中间件形式使用的话,这个对象就根据你所使用的框架而定。nuxt generate 不可用 |
redirect | Function | 客户端 & 服务端 | 用这个方法重定向用户请求到另一个路由。状态码在服务端被使用,默认 302 redirect([status,] path [, query]) |
error | Function | 客户端 & 服务端 | 用这个方法展示错误页:error(params) 。params 参数应该包含 statusCode 和 message 字段 |
nuxtState | Object | 客户端 | Nuxt 状态,在使用 beforeNuxtRender 之前,用于客户端获取 Nuxt 状态,仅在 universal 模式下可用 |
beforeNuxtRender(fn) | Function | 服务端 | 使用此方法更新 NUXT 在客户端呈现的变量,fn 调用 (可以是异步) { Components, nuxtState } ,参考 示例 |
使用中间件对用户进行身份验证,并允许他们在验证通过后访问页面
- // middle/authenticated.js
- /**
- * 每一个中间件需要放置在middleware/目录,文件名就是中间件名称authenticated中间件
- * param:context上下文对象
- * 中间件执行流程:
- * 1.nuxt.config.js
- * 2.匹配布局layouts
- * 3.匹配页面pages
- * 验证是否登录的中间件
- */
- export default function ({ store, redirect }) {
- // If the user is not authenticated
- if (!store.state.user) {
- return redirect('/login')
- }
- }
- // notAuthenticated.js
- export default function ({ store, redirect }) {
- // If the user is authenticated redirect to home page
- // 如果已登录 跳转到首页
- if (store.state.user) {
- return redirect('/')
- }
- }
-
使用,在对应layouts或pages中添加 middleware: 'authenticated'
Nuxt.js 允许在运行 Vue.js 应用程序之前执行 js 插件,需要使用自己的库或第三方模块时特别有用。
例如:封装一个axios请求插件
首先新增文件plugins/request.js
- /**
- * 基于axios封装的请求模块
- */
- import axios from 'axios'
-
- // 创建请求对象
- export const request = axios.create({
- baseURL: 'https://conduit.productionready.io/',
- timeout: 60000
- })
-
- // 通过插件机制获取到上下文对象
- // export default (context)
- export default ({ store }) => {
- // console.log(context)
- // 请求拦截器
- // 任何请求都要经过请求拦截器
- // 可以在拦截器中做公共业务处理 如设置token
- request.interceptors.request.use(function (config) {
- // Do something before request is sent
- // 请求就会经过这里
- /**
- * 需要拿到vuex中的user对象
- * import store from '@/store'
- * 因为store都是通过export按需导出
- * 需要按需加载import { state } from '@/store'
- * 此时拿到的state是一个函数 需要调用一下此函数
- * 这样拿到的数据永远是null
- * 不同于客户端渲染 所以需要放入到plugins中
- */
- const { user } = store.state
-
- if (user && user.token) {
- config.headers.Authorization = `Token ${user.token}`
- }
-
- // 返回 config 请求配置对象
- return config
- }, function (error) {
- // 如果请求失败(此时请求还没有发出去)就会进入这里
- // Do something with request error
- return Promise.reject(error)
- })
- }
然后, 在 nuxt.config.js
内配置 plugins
如下:
- module.exports = {
- plugins: ['~/plugins/request']
- }
模块是 Nuxt.js 扩展,可以扩展其核心功能并添加无限的集成。
Nuxt.js 会尝试找到 src 目录(默认是应用根目录)下的 store目录,如果该目录存在,它将做以下的事情:
nuxtServerInit 方法
- export const actions = {
- // nuxtServerInit 是一个nuxt提供的特殊的 action 方法
- // 这个 action 会在服务端渲染期间自动调用,且仅在服务端中运行
- // 作用:初始化容器数据,以及需要传递数据给客户端使用的数据
- // commit提交mutations的方法 req服务端渲染期间的请求对象
- nuxtServerInit ({ commit }, { req }) {
- if (req.session.user) {
- commit('user', req.session.user)
- }
- }
- }
下图阐述了 Nuxt.js 应用一个完整的服务器请求到渲染(或用户通过 <nuxt-link>
切换路由渲染页面)的流程:
调用nuxtServerInit方法,服务端操作store,实质上是一个Action
middleware中间件运行,可以使用在nuxt.config.js、layouts、pages中,会在页面初始化之前被调用
validate()校验参数,可以让你在动态路由对应的页面组件中配置一个校验方法用于校验动态路由参数的有效性。比如对路由参数验证失败,如果校验方法返回的值不为 true
, Nuxt.js 将自动加载显示 404 错误页面
asyncData方法会在组件(限于页面组件)每次加载之前被调用,是运行在page页面加载前的一个方法,这个方法只能在页面组件中使用,这个方法的作用很多,比如:ajax数据请求、操作state数据、页面重定向等等
asyncData运行在服务器实现render之前,因此就实现了服务端渲染
fetch方法与asyncData调用时机相同,不同之处在于asyncData既可以充应用的状态树(store)数据,也可以设置组件的数据,而fetch 方法用于在渲染页面前填充应用的状态树(store)数据, 不会设置组件的数据。
渲染页面
渲染完成之后开始执行vue的生命周期