目录
一、Nuxt.js 概述
1.1 我们做过的 SPA
- SPA (single page web application) 单页 Web 应用, Web 不再是一张张页面, 而是一个整体的应用, 一个由路由系统、数据系统、页面 (组件) 系统等等, 组成的应用程序。
- 我们之前学习的 Vue 就是 SPA 中的佼佼者。
- SPA 应用广泛用于对 SEO 要求不高的场景中
1.2 什么是 SEO
- SEO: 搜索引擎优化 (Search Engine Optimization), 通过各种技术 (手段) 来确保, 我们的 Web 内容被搜索引擎最大化收录, 最大化提高权重, 最终带来更多流量。
- 非常明显, SPA 程序不利于 SEO
- SEO 解决方案: 提前将 页面和数据 进行整合
- 前端: 采用 SSR
- 后端: 页面静态化 (freemarker、thymeleaf、velocity)
1.3 什么是 SSR 技术
- 服务端渲染 (Server Side Render), 既: 网页是通过服务端渲染生成后输出给客户端。
- 在 SSR 中, 前端分为 2 部分: 前端客户端、前端服务端
- 前端服务端, 用于发送 ajax, 获得数据
- 前端客户端, 用于将 ajax 数据和页面进行渲染, 渲染成 html 页面, 并响应给调用程序 (浏览器、爬虫)
- 如果爬虫获得 html 页面, 就可以启动处理程序, 处理页面内容, 最终完成 SEO 操作。
1.4 SPA 和 SSR 对比
SPA 单页应用程序 | SSR 服务器端渲染 | |
优势 |
|
|
劣势 |
|
|
1.5 什么是 Nuxt.js
- Nuxt.js 是一个基于 Vue.js 的通用应用框架。
- Nuxt 支持 vue 的所有功能, 此类内容为 前端客户端 内容。
- Nuxt 特有的内容, 都是 前端客户端 内容。
- 通过对客户端/服务端基础架构的抽象组织, Nuxt.js 主要关注的是应用的 UI 渲染。
- Nuxt.js 预设了利用 Vue.js 开发服务端渲染的应用所需要的各种配置。
二、入门案例
2.1 create-nuxt-app 介绍
- Nuxt.js 提供脚手架工具 create-nuxt-app
- create-nuxt-app 需要使用 npx
- npx 命令为 NPM 版本 5.2.0 默认安装组件
2.2 安装
npx create-nuxt-app <project-name>
- 例如
npx create-nuxt-app demo_nuxt02
2.3 启动
npm run dev
2.4 访问
三、目录结构
3.1 目录
目录名称 | 描述 |
assets | 资源目录, 用于存放需要编译的静态资源。例如: LESS、ASAA等, 默认情况下, Nuxt 使用 Webpack 若干加载处理器处理目录中的文件 |
components | vue 组件目录, Nuxt.js 不会增强该目录, 既不支持 SSR |
layouts | 布局组件目录 |
pages | 页面目录, 所有的 vue 试图, nuxt 根据目录结构自动生成对应的路由 |
plugins | 插件目录 |
static | 静态文件目录, 也就是不需要编译的文件 |
store | vuex 目录 |
nuxt.config.js | nuxt 个性化配置文件, 内容将覆盖默认 |
package.json | 项目配置文件 |
3.2 别名
- assets 资源的引用: ~ 或 @
// HTML 标签
<img src="~assets/13.jpg" style="height:100px;width:100px;" alt="">
<img src="~/assets/13.jpg" style="height:100px;width:100px;" alt="">
<img src="@/assets/13.jpg" style="height:100px;width:100px;" alt="">
// CSS
background-image: url(~assets/13.jpg);
background-image: url(~/assets/13.jpg);
background-image: url(@/assets/13.jpg);
- static 目录资源的引用: / 直接引用
//html标签
<img src="/12.jpg" style="height:100px;width:100px;" alt="">
//css
background-image: url(/12.jpg);
- 实例
<template>
<div>
<!-- 引用 assets 目录下经过 webpack 构建处理后的图片 -->
<img src="~assets/13.jpg" style="height:100px;width:100px;" alt="">
<!-- 引用 static 目录下的图片 -->
<img src="/12.jpg" style="height:100px;width:100px;" alt="">
<!-- css -->
<div class="img1"></div>
<div class="img2"></div>
</div>
</template>
<script>
export default {
}
</script>
<style>
.img1 {
height: 100px;
width: 100px;
background-image: url(~assets/13.jpg);
background-size: 100px 100px;
display: inline-block;
}
.img2 {
height: 100px;
width: 100px;
background-image: url(/12.jpg);
background-size: 100px 100px;
display: inline-block;
}
</style>
四、路由
4.1 路由概述
- Nuxt.js 依据 pages 目录结构自动生成 vue-router 模块的路由配置。
- 要在页面之间切换路由, 我们建议使用 <nuxt-link> 标签。
标签名 | 描述 |
<nuxt-link> | nuxt.js 中切换路由 |
<Nuxt /> | nuxt.js 的路由视图 |
<router-link> | vue 默认切换路由 |
<router-view/> | vue 默认路由视图 |
4.2 静态路由
- 自动生成基础路由规则--静态路由
路径 | 组件位置及其名称 | 规则 |
/ | pages/index.vue | 默认文件 index.vue |
/user | pages/user/index.vue | 默认文件 index.vue |
/user/one | pages/user/one.vue | 指定文件 |
- 实例
情况1:访问路径,由pages目录资源的名称组成(目录名称、文件的名称)
- 资源位置: ~/pages/user/one.vue
- 访问路径:http://localhost:3000/user/one
情况2:每一个目录下,都有一个默认文件 index.vue
- 资源位置: ~/pages/user/index.vue
- 访问路径:http://localhost:3000/user
- 思考: /user 可以匹配几种文件?
- pages/user.vue (优先级高)
- pages/user/index.vue 文件
4.3 动态路由
- 在 Nuxt.js 里面定义带参数的动态路由, 需要创建对应的以下划线作为前缀的 Vue 文件 或 目录。
路由中路径匹配 | 组件位置及其名称 |
/ | pages/index.vue |
/user/:id | pages/user/_id.vue |
/:slug | pages/_slug/index.vue |
/:slug/comments | pages/_slug/comments.vue |
- 实例1: 获得 id 值, 创建资源 user/_id.vue
<template>
<div>
查询详情 {{this.$route.params.id}}
</div>
</template>
<script>
export default {
transition: 'test',
mounted() {
console.info(this.$route)
},
}
</script>
<style>
</style>
4.4 动态命名路由
- 路径 /news/123 匹配 _id.vue 还是 _name.vue ?
- 我们可以使用 <nuxt-link> 解决以上问题
- 通过 name 确定组件名称: "xxx-yyy"
- 通过 params 给对应的参数传递值
<nuxt-link :to="{name:'news-id',params:{id:1002}}">第2新闻</nuxt-link>
<nuxt-link :to="{name:'news-name',params:{name:1003}}">第3新闻</nuxt-link>
4.5 默认路由
路径 | 组件位置及其名称 |
不匹配的路径 | pages/_.vue |
- 404 页面, 可以采用 _.vue 进行处理
4.6 嵌套路由 (了解)
- 创建嵌套子路由, 你需要添加一个父组件 Vue 文件, 同时添加一个与该文件同名的目录用来存放子视图组件。
- 要求: 父组件使用 <nuxt-child/> 显示子视图内容
pages/
--| book/ //同名文件夹
-----| _id.vue
-----| index.vue
--| book.vue //父组件
- 步骤1: 编写父组件 pages/child/book.vue
<template>
<div>
<nuxt-link to="/child/book/list">书籍列表</nuxt-link> |
<nuxt-link to="/child/book/123">书籍详情</nuxt-link> |
<hr>
<nuxt-child />
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
- 步骤2: 编写子组件 pages/child/book/list.vue
<template>
<div>书籍列表</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
- 步骤3: 编写子组件 pages/child/book/_id.vue
<template>
<div>书籍详情{{$route.params.id}} </div>
</template>
<script>
export default {
}
</script>
<style>
</style>
五、视图
5.1 默认模板 (了解)
- 定制化默认的 html 模板, 只需要在应用根目录下创建一个 app.html 的文件。
- 默认模板:
<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
<head {{ HEAD_ATTRS }}>
{{ HEAD }}
</head>
<body {{ BODY_ATTRS }}>
{{ APP }}
</body>
</html>
- 修改模板, 对低版本 IE 浏览器进行支持 (兼容 IE 浏览器)
<!DOCTYPE html>
<!--[if IE 9]><html lang="en-US" class="lt-ie9 ie9" {{ HTML_ATTRS }}><![endif]-->
<!--[if (gt IE 9)|!(IE)]><!--><html {{ HTML_ATTRS }}><!--<![endif]-->
<head {{ HEAD_ATTRS }}>
{{ HEAD }}
</head>
<body {{ BODY_ATTRS }}>
{{ APP }}
</body>
</html>
5.2 默认布局 (掌握)
5.2.1 布局概述
- 布局: Nuxt.js 根据布局, 将不同的组件进行组合。
- 模板: html 页面, 是布局后所有组件挂载的基础。
5.2.2 布局分析
- layouts/default.vue 默认布局组件
- 访问路径根据路由, 确定执行组件
- 组件具体显示的位置, 由布局来确定
5.2.3 公共导航
- 修改 layouts/default.vue
<template>
<div>
<nuxt-link to="/">首页</nuxt-link> |
<nuxt-link to="/user/login">登录</nuxt-link> |
<nuxt-link to="/user/123">详情</nuxt-link> |
<nuxt-link to="/about">默认页</nuxt-link> |
<nuxt-link to="/nuxt/async">async</nuxt-link> |
<hr/>
<Nuxt />
</div>
</template>
5.3 自定义布局
- 在 layouts 目录下创建组件: layouts/blog.vue
<template>
<div>
开头<br/>
<nuxt/>
结束<br/>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
- 在需要的视图中使用 blog 布局
<script>
export default {
layout: 'blog'
//...
}
</script>
5.4 错误页面
- 编写 layouts/error.vue 页面, 实现个性化错误页面
<template>
<div>
<div v-if="error.statusCode == 404">
404 页面不存在 {{error.message}}
</div>
<div v-else>
应用程序错误
</div>
<nuxt-link to="/">首 页</nuxt-link>
</div>
</template>
<script>
export default {
props: ['error']
}
</script>
<style>
</style>
- 解决问题: 404、500、连接超时 (服务器关闭)
- 总结: 所学习的技术中, 有 2 种方式处理错误页面
- 方式1: 默认路径, _.vue (先执行)
- 方式2: 错误页面, ~/layouts/error.vue
5.5 Nuxt 组件特殊配置
- 页面组件实际上是 Vue 组件, 只不过 Nuxt.js 为这些组件添加了一些特殊的配置项
特殊配置项 | 描述 |
asyncData | SSR 进行异步数据处理, 也就是服务器端 ajax 操作区域。 |
fetch | 在渲染页面之前获取数据填充应用的状态树 (store) |
head | 配置当前页面的 head 标签, 整个第三方 css、js 等。 |
layout | 指定当前页面使用的布局 |
transition | 指定页面切换的过度动效 |
scrollTo Top | 布尔值, 默认: false。用于判定渲染页面前是否需要将当前页面滚动至顶部。 |
5.5.1 模板代码
<template>
<h1 class="red">Hello {{ name }}!</h1>
</template>
<script>
export default {
//异步处理数据, 每次加载之前被调用
asyncData (context) {
// called every time before loading the component
return { name: 'World' }
},
//用于在渲染页面之前获取数据填充应用的状态树(store)
fetch () {
// The fetch method is used to fill the store before rendering the page
},
//配置当前页面的 Meta 标签
head: {
// Set Meta Tags for this Page
},
// and more functionality to discover
...
}
</script>
<style>
.red {
color: red;
}
</style>
5.5.2 head
- html 模板代码
<html>
<head>
<meta charset="UTF-8" />
<title>我是标题</title>
<link rel="stylesheet" type="text/css" href="css外部文件"/>
<script src="js外部文件" type="text/javascript" charset="utf-8"></script>
</head>
<body>
</body>
</html>
- 通过 nuxt 提供 head 属性, 可以给单个设置: 标题、外部 css、外部 js 等内容。
<template>
<div>
详情页 {{$route.params.id}} <br/>
<div class="bg2"></div>
<div class="bg3"></div>
</div>
</template>
<script>
export default {
head: {
title: '详情页',
link: [
{rel:'stylesheet',href:'/style/img.css'},....
],
script: [
{ type: 'text/javascript', src: '/js/news.js' }
]
}
}
</script>
<style>
.bg2 {
background-image: url('~static/img/2.jpg');
width: 300px;
height: 300px;
background-size: 300px;
}
</style>
六、ajax 操作
6.1 整合 axios
6.1.1 默认整合
- 在构建项目时, 如果选择 axios 组件, nuxt.js 将自动与axios 进行整合
6.1.2 手动整合 (可选)
- 步骤1: package.json 有 axios 的版本
"dependencies": {
"@nuxtjs/axios": "^5.13.1",
},
- 步骤2: 安装
npm install
- 步骤3: nuxt.config.js 以模块的方式添加 axios
modules: [
// https://go.nuxtjs.dev/axios
'@nuxtjs/axios',
],
6.1.3 常见配置
- 修改 nuxt.config.js 进行 baseURL 的配置
// Axios module configuration: https://go.nuxtjs.dev/config-axios
axios: {
baseURL:'http://localhost:10010/'
},
6.2 使用 axios 发送 ajax
- 在 vue 页面中, 通过 this.$axios.xxx() 操作 ajax。this.$axios 与之前 axios 等效。
this.$axios.post("/search-service/search",this.searchMap).then( res => {
//获得查询结果
this.searchResult = res.data.data;
});
6.3 使用 asyncData 发送 ajax
- asyncData 中的 ajax 将在 "前端服务端执行", 在浏览器看到是数据, 而不是 ajax 程序。
6.3.1 发送一次请求
- 语法
export default {
async asyncData( context ) { //context就相当于其他地方的this
//发送ajax
let { data } = await context.$axios.get('路径','参数')
// 返回结果
return {变量: 查询结果从data获取 }
},
}
- 实例
<template>
<div>{{echo}}</div>
</template>
<script>
export default {
async asyncData(context) {
// 发送ajax
let {data} = await context.$axios.get('/service-consumer/feign/echo/abc')
// 返回数据
return {
echo: data
}
},
}
</script>
<style>
</style>
6.3.2 发送多次请求
- 语法1
export default {
async asyncData( content ) {
let [结果1,结果2] = await Promise.all([ ajax请求1, ajax请求2])
return {
变量1: 结果1,
变量2: 结果2
}
},
}
- 语法2
export default {
async asyncData( content ) {
let [{数据:别名1},{数据:别名2}] = await Promise.all([ ajax请求1, ajax请求2])
return {
变量1: 别名1,
变量2: 别名2
}
},
}
//演化过程
let response = ajax请求
let [response,response] = await Promise.all([ajax1,ajax2])
let [{data},{data}] = await Promise.all([ajax1,ajax2])
let [{data:别名1},{data:别名2}] = await Promise.all([ajax1,ajax2])
- 实例
<template>
<div>{{echo}} {{echo2}}</div>
</template>
<script>
export default {
async asyncData(context) {
// 发送ajax
let [{data:echo}, {data:echo2}] =
await Promise.all([
context.$axios.get('/service-consumer/feign/echo/abc'),
context.$axios.get('/service-consumer/client/echo/abc')
])
// 返回数据
return {
echo,
echo2
}
},
}
</script>
<style>
</style>
6.4 代码执行时机
6.4.1 服务器端执行的代码
- Nuxt 是一个 SSR (服务端渲染) 的框架, 那么什么叫服务器端渲染?
- 就是 代码是在服务端 执行的, 浏览器中看到的结果, 是代码在服务端执行完之后的代码
- 前端项目客户端渲染:
- 浏览器解析 js 代码
- 前端项目服务器端渲染:
- node 服务器解析 js 代码, if、for 都在服务器执行解析完成, 发 HTML 页面给客户端
6.4.1.1 页面中已经包含了数据
- 由于代码是在服务端执行的, 所以客户端看到的页面中已经存在数据了, 这样有利于 SEO。比如, 我们创建 pages/test.vue 的页面
<template>
<div>
<ul>
<li v-for="(v,k) in goods" :key="k">
商品名称:{{ v.name }}
价格:{{ v.price }}
</li>
</ul>
</div>
</template>
<script>
export default {
data:function(){
return {
token: localStorage.getItem('toke'),
goods:[
{
'name':'iphon x',
'price':8999
},
{
'name':'mac pro 13',
'price': 7000
}
]
}
}
}
</script>
- 浏览页面, 并查看页面的源代码, 会发现数据已经在页面中了 (原本使用的 vue 的页面中是没有数据的!)
6.4.1.3 不能执行客户端代码
- 当使用客户端特有的代码时会报错, 比如: alert、localStorage、window、location、document 等只有浏览器才有的。比如, 以下代码在运行时会报错: localStorage is no defined
6.4.2 客户端运行的代码
- Nuxt 中的代码也并不都是运行在服务器端, 有一部分函数中的代码是在客户端执行的。
6.4.2.1 客户端触发的函数
- 由客户端触发的函数, 比如, 页面中有一个按钮, 在点击时触发一个函数, 这个函数中的代码就是在客户端执行的, 因为只有在客户端才能被点击, 所以在这个函数中使用 alert 就正常。
<template>
<div>
<button @click="clickMe">点我</button>
</div>
</template>
<script>
export default {
methods:{
clickMe:function(){
alert('clicked !')
}
}
}
</script>
6.4.2.2 生命周期钩子函数
- 8 个生命周期函数
- 创建前 beforeCreated: 执行两次, 服务端和客户端都会执行一次
- 创建后 created: 执行两次, 服务端和客户端都会执行一次
- 挂载前 beforeMount
- 挂载后 mounted
- 更新前 beforeUpdate
- 更新后 updated
- 销毁前 beforeDestroy
- 销毁后 destroyed
- Vue 中有 8 个生命周期钩子函数, 其中除了 beforeCreated 和 created 2个之外, 其它的 6 个基本都是在客户端执行的。beforeCreated 和 created 这两个钩子函数会在服务器和客户端分别被执行一次。
- 以前我们获取页面初始数据时都是写在 created 函数中的, 但是因为 created 会在服务器和客户端分别被执行一次 (共执行两次), 所以我们一般使用 mounted (挂载前) 来初始化页面数据。
export default {
data:function(){
return {
carts:[]
}
},
// 获取初始数据
mounted:function(){
axios.get('/carts').then((res)=>{
this.carts = res.data.data
})
}
}
6.5 js 插件执行时机
6.5.1 客户端
- 步骤1: 在 nuxt.conf.js 中配置客户端插件, 设置 mode 为 client
plugins: [
{ src: '~plugins/api.js', mode: 'client' }
//{ src: '~plugins/api.js', ssr: false }
],
- 步骤2: 编写 plugins/api.js 对内置的 $axios 进行增强
//自定义函数
const request = {
test : (params) => {
return axios.get('/service-consumer/feign/echo/abc',{
params
})
},
}
var axios = null
export default ({ $axios }, inject) => {
//3) 保存内置的axios
axios = $axios
//4) 将自定义函数交于nuxt
// 使用方式1:在vue中,this.$request.xxx()
// 使用方式2:在nuxt的asyncData中,content.app.$request.xxx()
inject('request', request)
}
6.5.2 服务端
- 步骤1: 配置服务端插件, 设置 mode 为 server
plugins: [
{ src: '~plugins/api.js', mode: 'client' },
{ src: '~plugins/api.server.js', mode: 'server' },
//{ src: '~plugins/api.js', ssr: false },
//{ src: '~plugins/api.server.js', ssr: true }
],
- 步骤2: 编写 plugins/api.server.js 对内置的 $axios进行增强
const request = {
test : (params) => {
return axios.get('/service-consumer/feign/echo/abc',{
params
})
},
}
var axios = null
export default ({ $axios, redirect, process }, inject) => {
//赋值
axios = $axios
//4) 将自定义函数交于nuxt
// 使用方式1:在vue中,this.$requestServer.xxx()
// 使用方式2:在nuxt的asyncData中,content.app.$requestServer.xxx()
inject('requestServer', request)
}
- 注意: 前端服务端插件, 不支持切换路由。也就是说刷新可以访问, 使用 <nuxt-link> 切换不能访问。解决方案: 修改mode, 支持 client 和 service。
6.5.3 插件配置总结
//方式1:通过src设置文件,通过mode设置模式
plugins: [
{ src: '~/plugins/apiclient.js', mode: 'client' }, //前端客户端
{ src: '~/plugins/apiserver.js', mode: 'server' }, //前端服务端
{ src: '~/plugins/api.js' } //前端客户端 + 前端服务端
]
//方式2:通过命名来确定模式
plugins: [
'~/plugins/api.client.js', //前端客户端
'~/plugins/api.server.js', //前端服务端
'~/plugins/api.js', //前端客户端 + 前端服务端
]
七、Vuex 状态树
7.1 根模块数据操作
- 步骤1: 创建 store/index.js 添加一个 counter 变量, 并可以继续累加操作
export const state = () => ({
counter: 0
})
export const mutations = {
increment (state) {
state.counter++
}
}
- 步骤2: 在页面中, 使用
<template>
<div>
首页 {{counter}}
<input type="button" value="+" @click="increment"/>
</div>
</template>
<script>
import { mapState,mapMutations } from 'vuex'
export default {
computed: {
...mapState(['counter'])
},
methods: {
...mapMutations(['increment'])
},
}
</script>
<style>
</style>
7.2 其他模块数据操作
- 步骤1: 创建其他模块 store/book.js
export const state = () => ({
money: 0
})
export const mutations = {
addmoney (state) {
state.money += 5
}
}
- 步骤2: 使用指定模块中的数据
<template>
<div>
首页 {{money}}
<input type="button" value="+" @click="addmoney" />
</div>
</template>
<script>
import { mapState,mapMutations } from 'vuex'
export default {
computed: {
money () {
return this.$store.state.book.money
}
},
methods: {
...mapMutations({
addmoney: 'book/addmoney'
})
},
}
</script>
<style>
</style>
7.3 完整 vuex 模板
// state为一个函数, 注意箭头函数写法
const state = () => ({
user: 'jack'
})
// mutations为一个对象
const mutations = {
setUser(state, value) {
state.counter = value
}
}
// action执行mutation
const actions = {
userAction (context,value){
// 可以发送ajax
context.commit('setUser',value)
}
}
// 获取数据
const getters = {
getUser (state) {
return state.user
}
}
export default {
namespace: true, // 命名空间,强制要求,在使用时,加上所属的模块名,例如:book/addmoney
state,
mutations,
actions,
getters
}
八、nuxt 流程总结