我们把自己,用终将灰飞烟灭的纸币,寂寞地捆绑起来
我搜寻着记忆
给我留下长久回味的时刻
让我难以忘怀的分分秒秒,他们统统不是来自金钱或者财富
一、概述
1. Nuxt.js是什么
- 一个基于Vue.js生态的第三方开源服务端渲染应用框架
- 它可以帮我们轻松的使用Vue.js技术栈构建同构应用
- 官网: Nuxt.js - Vue.js 通用应用框架 | Nuxt.js 中文网
- Github仓库: https://github.com/nuxt/nuxt.js
2. NuxtJS 使用方式
- 初始项目,从零开始的方式
- 已有的 Node.js 服务端项目的方式
- 直接把 Nuxt 当作一个中间件集成到 Node Web Server 中
- 现有的 Vue.js 项目基础上增加Nuxt的方式
- 非常熟悉 Nuxt.js
- 至少百分之 10 的代码改动
二、初始化 NuxtJS
Nuxt 提供了两种方式用来创建项目: 使用 create-nuxt-app 脚手架工具、手动创建,下面以手动创建为例:
1. 创建项目目录
# 创建项目
mkdir nuxt-app-demo
# 进入项目
cd nuxt-app-demo
# 初始化 package.json 文件
npm init -y
# 安装 nuxt
npm install nuxt
2. 配置项目启动命令
在 package.json
文件的 scripts
中新增:
"scripts": {
"dev": "nuxt"
}
3. 创建入口文件
根路径创建 pages
目录:
mkdir pages
创建第一个页面的 pages/index.vue
:
<template>
<h1>Hello world!</h1>
</template>
4. 启动项目
npm run dev
- 注意:Nuxt.js 会监听 pages 目录中的文件更改,因此在添加新页面时无需重新启动应用程序。
5. git托管
新建 .gitignore
文件:
# 排除不需要用git托管的文件
node_modules
.nuxt
6. git 分支
git branch
# 看本地当前所在分支,并且在当前分支前面加“*”号标记
git branch -r
# 查看远程分支,r是remote的简写
git branch _分支名
# 创建一个新的本地分支
git checkout 分支名
# 切换分支
git checkout -b 分支名
# 创建并切换分支
三、路由
1. 基本路由
⑴. 文档说明
⑵. 项目代码:
新建 pages/user/one
文件:
<template>
<h1>one</h1>
</template>
⑶. 预览:
2. 路由导航
- a 标签: 会刷新整个页面,走服务端渲染,不要使用
- nuxt-link:
<nuxt-link to="/">
主页</nuxt-link>
- 编程式导航:
this.$router.push
编辑 pages/user/one
文件:
<template>
<div>
<h1>About page</h1>
<!-- a 链接,刷新导航,走服务端渲染 -->
<h2>a 链接</h2>
<a href="/">首页</a>
<!-- router-link 导航链接组件 -->
<h2>router-link</h2>
<router-link to="/">首页</router-link>
<!-- 编程式导航 -->
<h2>编程式导航</h2>
<button @click="onClick">首页</button>
</div>
</template>
<script>
export default {
name: 'AboutPage',
methods: {
onClick () {
this.$router.push('/')
}
}
}
</script>
3. 动态路由
动态路由指的是路径时动态的,路径中的某一部分是动态变化的,
需要用:
进行表征,使用?
表示是否必要
在 Nuxt.js 里面定义带参数的动态路由,需要创建对应的以下划线作为前缀的 Vue 文件 或 目录。
名称为 users-id 的路由路径带有 :id? 参数,表示该路由是可选的。如果你想将它设置为必选的路由,需要在 users/_id 目录内创建一个 index.vue 文件。
新建 pages/user/_id.vue
文件:
<template>
<div>
<h1>User Pages</h1>
<!-- 路由参数 id 能够通过 $route.params.xx 的形式来获取 -->
<h2>{{ $route.params.id }}</h2>
</div>
</template>
<script>
export default {
name: 'UserPage'
}
</script>
4. 嵌套路由
- 创建内嵌子路由,你需要添加一个 Vue 文件,同时添加一个与该文件同名的目录用来存放子视图组件
- 在父组件(.vue文件) 内增加 用于显示子视图内容
5. 自定义路由配置
新增一个 nuxt.config.js
文件进行配置:
/**
* Nuxt.js 配置文件
*/
module.exports = {
router: {
base: '/abc'
}
}
// 注意,这样配置后路由地址都应该以/abc开头:http://localhost:3000/abc/...
// 要是访问首页的话就是abc/,末尾的/不能省略
也可以使用 extendRoutes
方法
module.exports = {
router: {
base: '/abc',
// routes: 一个数组,路由配置表
// resolve: 解析路由组件路径
extendRoutes(routes, resolve) {
routes.push({
name: 'hello',
path: '/hello',
component: resolve(__dirname, 'pages/about.vue')
})
}
}
}
四、视图
1. 结构
在 NuxtJS 中页面结构一般由三部分组成:
- 第一部分是最外层的文档页面,也就是单页面或者说服务端渲染的HTML页面。
- 在HTML 页面里面包裹着 Layout布局组件(可选),相当于所有页面的父路由。
- 再往里面是页面组件,每个页面组件有自己额外的成员方法,包括页面的子组件之类的可选内容。
2. 模板
定制化默认的 html 模板,只需要在根目录(或者 src)创建一个 app.html
的文件:
<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
<head {{ HEAD_ATTRS }}>
{{ HEAD }}
</head>
<body {{ BODY_ATTRS }}>
{{ APP }} <!-- 这body里就是渲染视图的位置,这个app就是根组件 -->
</body>
</html>
3. 布局 (Layout)
可通过添加 layouts/default.vue
文件来扩展应用的默认布局:
<template>
<div>
<h2>layouts/default.vue</h2>
<!-- 页面出口,类似子路由 -->
<nuxt />
</div>
</template>
然后其他页面 (即 pages/index.vue
) 使用自定义布局:
<template>
<h1>Hello world!</h1>
</template>
<script>
export default {
name: 'HomePage',
// 在别的组件使用时指定layout:'foo' 也就是指定别的组件作为布局组件,也就是foo会代替default
layout: 'default' // 表示使用默认布局使用的是 layouts文件夹下面的default组件
}
</script>
五、异步数据
1. asyncData 方法
asyncData方法会在组件(限于页面组件)每次加载之前被调用。它可以在服务端或路由更新之前被调用。在这个方法被调用的时候,第一个参数被设定为当前页面的上下文对象,你可以利用 asyncData方法来获取数据,Nuxt.js 会将 asyncData 返回的数据融合组件 data 方法返回的数据一并返回给当前组件。
-
基本用法
- 它会将asyncData返回的数据融合组件data方法返回数据一并给组件
- 调用时机:服务端渲染期间和客户端路由更新之前(保证了服务端和客户端都要运行处理数据)
-
注意事项
- 只能在页面组件中使用,非页面组件中不会调用asyncData方法,如果子组件中需要数据,可以通过props方式传递数据
- 没有this,因为它是在组件初始化之前被调用的
当你想用的动态页面内容有利于SEO或者是提升首屏渲染速度的时候,就在asyncData中发送请求数据。如果是非异步数据或者普通数据,则正常的初始化到data中即可。
2. 代码演示
⑴. 添加数据
新建数据文件 static/data.json
:
{
"posts": [
{
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
},
{
"id": 2,
"title": "qui est esse",
"body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
},
{
"id": 3,
"title": "ea molestias quasi exercitationem repellat qui ipsa sit aut",
"body": "et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut"
},
{
"id": 4,
"title": "eum et est occaecati",
"body": "ullam et saepe reiciendis voluptatem adipisci\nsit amet autem assumenda provident rerum culpa\nquis hic commodi nesciunt rem tenetur doloremque ipsam iure\nquis sunt voluptatem rerum illo velit"
},
{
"id": 5,
"title": "nesciunt quas odio",
"body": "repudiandae veniam quaerat sunt sed\nalias aut fugiat sit autem sed est\nvoluptatem omnis possimus esse voluptatibus quis\nest aut tenetur dolor neque"
}
],
"title": "文章数据"
}
⑵. 获取数据
安装 axios
:
npm i axios
- 在首页直接获取数据(服务端获取异步数据):
<template>
<div>
<h1>{{ title }}</h1>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'HomePage',
layout: 'default',
async asyncData () {
console.log('asyncData')
console.log(this)
const res = await axios({
method: 'GET',
url: 'http://localhost:3000/data.json'
})
return res.data
},
data () {
return {
foo: 'bar'
}
}
}
</script>
如果我们尝试在asyncData里面进行console.log(‘xxx’)时,会发现在服务端的控制台输出xxx的同时,nuxt为了方便调试和更加直观,会让客户端也会输出xxx,并且客户端的xxx是包裹在Nuxt SSR对象中的,如果尝试打印this会发现是undefined,因为服务端渲染时组件还没初始化。
⑶. 客户端路由导航
先通过地址访问首页,会发现客户端控制台打印Nuxt SSR下面输出了xxx,说明首屏是在服务端渲染的,然后输出了xxx。紧接着通过about路由导航链接跳转到about页面,然后about页面中也有个导航链接可以跳转到首页,此时跳转回首页时也会调用首页组件index.vue中的asyncData方法,此时也会打印xxx,但是这时候的xxx外层没有Nuxt SSR对象了,原因是这次导航跳转引起的调用是在客户端执行的
首屏服务端渲染数据好理解,直接拿到数据返回并返回了SPA页面脚本,但是如果客户端执行SPA程序时,不执行asyncData的话,将会导致数据无法更新,所以当我们通过导航链接进行跳转时也会在客户端进行一次调用asyncData
⑷. 子组件调用 asyncData 方法
注意: asyncData只能在页面组件中使用,不能在非页面组件(比如页面组件的子组件)中使用,非页面组件中不会调用asyncData函数如果想在子组件使用服务端渲染时的数据,只能通过页面组件进行获取,然后再利用父子组件中的传值方法来传递给页面组件的子组件
页面组件的子组件:
// components/Foo.vue
<template>
<div>
<h1>FooPage</h1>
<ul>
<li
v-for="item in posts"
:key="item.id"
>
{{ item.title }}
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'FooPage',
props: ['posts'],
// asyncData 只能在页面组件中使用
// async asyncData () {
// console.log('foo asyncData')
// return {
// foo: 'bar'
// }
// }
}
</script>
页面组件:
// pages/index.vue
<template>
<div>
<h1>{{ title }}</h1>
<nuxt-link to="/about">About</nuxt-link>
<br>
<foo :posts="posts" />
</div>
</template>
<script>
import axios from 'axios'
import Foo from '@/components/Foo'
export default {
name: 'HomePage',
layout: 'default',
components: {
Foo
},
// 当你想要动态页面内容有利于 SEO
// 或者是提升首屏渲染速度的时候,
// 就在 asyncData 中发请求拿数据
async asyncData () {
console.log('asyncData')
console.log(this)
const res = await axios({
method: 'GET',
url: 'http://localhost:3000/abc/data.json'
})
return res.data
},
// 如果是非异步数据或者普通数据,则正常的初始化到 data 中即可
data () {
return {
foo: 'bar'
}
}
}
</script>
3. 上下文对象
比如:
http:// localhost:3000/artical/5
这个路径就应该对应第五篇文章,那么问题是怎么获取这个5呢
首先我们容易想到路由对象this.$route.params
,但是asyncData
在服务端执行时是没有this
的,也就是说this
不指向当前Vue
实例
所以我们需要使用asyncData
中的上下文对象参数来获取,上下文对象中可以获取动态路由中的参数
// pages/article/_id.vue
// 根据路径中的最后一位id值的不同来拿到不同的对象,然后渲染页面中的不同内容
<template>
<div>
<!-- <h1>article page</h1> -->
<h1>{{ article.title }}</h1>
<div>{{ article.body }}</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'ArticlePage',
// asyncData 上下文对象
async asyncData (context) {
// 这里有我们需要的数据
console.log(context)
const { data } = await axios({
method: 'GET',
url: 'http://localhost:3000/data.json'
})
// asyncData 里面没有 this, 不能通过这种方式获取 id
// console.log(this.$route.params)
// 可以通过上下文对象的 params.id 或者 router.params.id
// 拿到后将字符串类型转换为数字类型
// 根据路径中的最后一位id值的不同来拿到不同的对象,然后渲染页面中的不同内容
const id = Number.parseInt(context.params.id)
return {
article: data.posts.find(item => item.id === id)
}
}
}
</script>