小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
Nuxt 是什么?
Nuxt.js 是一个基于 Vue.js 的通用应用框架。
通过对客户端/服务端基础架构的抽象组织,Nuxt.js 主要关注的是应用的 UI 渲染。
我们的目标是创建一个灵活的应用框架,你可以基于它初始化新项目的基础结构代码,或者在已有 Node.js 项目中使用 Nuxt.js。
Nuxt.js 预设了利用 Vue.js 开发服务端渲染的应用所需要的各种配置。
除此之外,我们还提供了一种命令叫:nuxt generate ,为基于 Vue.js 的应用提供生成对应的静态站点的功能。
我们相信这个命令所提供的功能,是向开发集成各种微服务(Microservices)的 Web 应用迈开的新一步。
作为框架,Nuxt.js 为 客户端/服务端 这种典型的应用架构模式提供了许多有用的特性,例如异步数据加载、中间件支持、布局支持等。
目录结构
Nuxt.js 的默认应用目录架构提供了良好的代码分层结构,适用于开发或大或小的应用。当然,你也可以根据自己的偏好组织应用代码。
pages目录
Nuxt.js 会依据 pages 目录中的所有 *.vue 文件生成应用的路由配置
资源(assets)目录
资源目录 assets 用于组织未编译的静态资源如 LESS、SASS 或 JavaScript。
- assets目录,会被webpack打包
- 访问路径,~/assets/路径
- 最终是使用require(路径)
<template>
<div>
<img src="~assets/img/芭芭拉.gif" alt="" />
<img src="~/assets/img/芭芭拉.gif" alt="" />
<div class="box">
<div class="box1"></div>
<div class="box2"></div>
</div>
<div class="inline" :style="keli"></div>
</div>
</template>
<script>
const keli = require(`~/assets/img/可莉.gif`)
export default {
data() {
return {
keli:`background: url(${keli}) no-repeat;`
}
},
}
</script>
<style>
img {
width: 200px;
}
.box1 {
display: inline-block;
width: 200px;
height: 200px;
background: url(~assets/img/甘雨.gif) no-repeat;
background-size: contain;
}
.box2 {
display: inline-block;
width: 200px;
height: 200px;
background: url(~/assets/img/甘雨.gif) no-repeat;
background-size: contain;
}
.inline {
width: 200px;
height: 200px;
background-size: contain !important;
}
</style>
组件(components)目录
组件目录 components 用于组织应用的 Vue.js 组件。Nuxt.js 不会扩展增强该目录下 Vue.js 组件,即这些组件不会像页面组件那样有 asyncData 方法的特性。
布局(layouts)目录
布局目录 layouts 用于组织应用的布局组件。 若无额外配置,该目录不能被重命名
默认的布局文件是 layouts/default.vue
布局文件中,使用 <Nuxt />可以看成 vue-router 的占位,把路由匹配到页面组件放到的该位置
中间件目录(middleware)目录
middleware 目录用于存放应用的中间件。
插件(plugins)目录
插件目录 plugins 用于组织那些需要在 根vue.js应用 实例化之前需要运行的 Javascript 插件。
静态文件(static)目录
静态文件目录 static 用于存放应用的静态文件,此类文件不会被 Nuxt.js 调用 Webpack 进行构建编译处理。服务器启动的时候,该目录下的文件会映射至应用的根路径 / 下。
举个例子: /static/robots.txt 映射至 /robots.txt
- 该目录下的文件,不会被webpack打包
- 访问时直接写static目录下的文件路径, (譬如) /imgs/xxx
- 无论是assets还是static 使用行内样式加载图片资源时,全部统一写法: require(~/静态资源目录/路径)
<template>
<div>
<img src="/img/芭芭拉.gif" alt="" />
<img src="/img/芭芭拉.gif" alt="" />
<div class="box">
<div class="box1"></div>
<div class="box2"></div>
</div>
<div class="inline" :style="keli"></div>
</div>
</template>
<script>
// const keli = require(`~img/可莉.gif`)
// 尽量使用英文文件名
const keli = require('~/static/img/1.gif')
export default {
data() {
return {
keli: `background: url(${keli}) no-repeat;`,
}
},
}
</script>
<style>
img {
width: 200px;
}
.box1 {
display: inline-block;
width: 200px;
height: 200px;
background: url(/img/甘雨.gif) no-repeat;
background-size: contain;
}
.box2 {
display: inline-block;
width: 200px;
height: 200px;
background: url(/img/甘雨.gif) no-repeat;
background-size: contain;
}
.inline {
width: 200px;
height: 200px;
background-size: contain !important;
}
</style>
Store 目录
store 目录用于组织应用的 Vuex 状态树 文件。 Nuxt.js 框架集成了 Vuex 状态树 的相关功能配置,在 store 目录下创建一个 index.js 文件可激活这些配置。
nuxt.config.js 文件
nuxt.config.js 文件用于组织 Nuxt.js 应用的个性化配置,以便覆盖默认配置。
package.json 文件
package.json 文件用于描述应用的依赖关系和对外暴露的脚本接口。
别名
别名 目录 ~ 或 @ srcDir ~~ 或 @@ rootDir 默认情况下,srcDir 和 rootDir 相同。
提示: 在您的 vue 模板中, 如果你需要引入 assets 或者 static 目录, 使用 ~/assets/your_image.png 和 ~/static/your_image.png方式。
路由
Nuxt.js 依据 pages 目录结构自动生成 vue-router 模块的路由配置。
要在页面之间使用路由,我们建议使用nuxt-link 标签。
基础路由
假设 pages 的目录结构如下:
pages/
--| user/
-----| index.vue
-----| _id.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/?id',
component: 'pages/user/_id.vue'
}
]
}
路由传参
很多时候我们需要在路由上传参数,路由上的参数有两种
- 路径参数:/about/100
- 查询参数: /about?id=100&name=李四
方式1:查询参数
| 路径 | 对应文件 | 页面中接受 |
|---|---|---|
| /about?hello=100 | pages/about.vue | $route.query.hello |
| /about/?hello=123&jack=456 | pages/about.vue | route.query.jack |
方式2:路径参数
| 路径 | 对应文件 | 页面中接受 |
|---|---|---|
| /about/123 | /goods/_id.vue | $route.params.id |
| /goods/100 | /goods/_i.vue | $route.params.i |
| /goods/100/200 | /goods/_cid/_gid | route.params.gid |
目录结构
├─pages
| ├─index.vue
| ├─user.vue
| ├─user
| | ├─child1.vue
| | └child2.vue
| ├─about
| | ├─_id.vue
| | ├─_cid
| | | └_gid.vue
├─layouts
| └default.vue
pages/about/_.id.vue
<template>
<div>
<h1>查询参数</h1>
<h2>{{ $route.query.hello }}</h2>
<h2>{{ $route.query.jack }}</h2>
<h1>路径参数</h1>
<h2>{{ $route.params.id }}</h2>
</div>
</template>
pages/about/_cid/_gid.vue
<template>
<div>
<h1>路径参数</h1>
<h2>{{ $route.params.cid }}</h2>
<h2>{{ $route.params.gid }}</h2>
</div>
</template>
layouts/default.vue
<template>
<div>
<nuxt-link to="/">Home</nuxt-link>
<nuxt-link to="/user">user</nuxt-link>
<br />
<nuxt-link to="/about/123">路径参数/about/123</nuxt-link>
<nuxt-link to="/about/123/456">路径参数/about/123/456</nuxt-link>
<nuxt-link to="/about/?hello=123&jack=456"
>查询参数/about/?hello=123&jack=456</nuxt-link
>
<!-- 显示 pages/index.vue -->
<Nuxt></Nuxt>
</div>
</template>
总结:
使用区别:
- 查询参数? 不利于SEO, 百度再抓取我们页面时,如果用? 只会抓取一个页面
- content/?id=1
- content/?id=2
- content/?id=3
- 推荐使用路径参数
路由参数校验
Nuxt.js 可以让你在动态路由组件中定义参数校验方法。
pages/users/_id.vue
export default {
validate({ params }) {
// 必须是number类型
return /^\d+$/.test(params.id)
}
}
如果校验方法返回的值不为 true或Promise中 resolve 解析为false或抛出 Error , Nuxt.js 将自动加载显示 404 错误页面或 500 错误页面。
// pages/index.vue
<template>
<div>
<h1>home</h1>
<nuxt-link to="/user/123">/user/123</nuxt-link>
</div>
</template>
// pages/user/_id.vue
<template>
<div>
<h1>传过来的参数{{ $route.params.id }}</h1>
</div>
</template>
嵌套路由
你可以通过 vue-router 的子路由创建 Nuxt.js 应用的嵌套路由。
创建内嵌子路由,你需要添加一个 Vue 文件,同时添加一个与该文件同名的目录用来存放子视图组件。
别忘了在父组件(.vue文件) 内增加 <nuxt-child/> 用于显示子视图内容。
一个小案例
目录结构
├─pages
| ├─index.vue
| ├─user.vue
| ├─user
| | ├─child1.vue
| | └child2.vue
├─layouts
| └default.vue
pages/index.vue
<template>
<div>
<h1>index首页</h1>
</div>
</template>
pages/user.vue
<template>
<div>
<h1>user</h1>
<nuxt-link to="/user/child1">child1</nuxt-link>
<nuxt-link to="/user/child2">child2</nuxt-link>
<!-- 注意父级 user 写上 nuxt-child 虽然 user/index.vue 也能匹配路由 /user 但是需要 user.vue 在父级才可以使用 nuxt-child -->
<nuxt-child></nuxt-child>
</div>
</template>
pages/child1.vue pages/child2.vue
<template>
<div><h1>child1</h1></div>
</template>
layouts/default.vue
<template>
<div>
<nuxt-link to="/">Home</nuxt-link>
<nuxt-link to="/user">user</nuxt-link>
<!-- 显示 pages/index.vue -->
<Nuxt></Nuxt>
</div>
</template>
<style>
html,
body {
text-align: center;
}
</style>
未知嵌套深度的动态嵌套路由
如果您不知道 URL 结构的深度,您可以使用_.vue动态匹配嵌套路径。这将处理与更具体请求不匹配的情况。
| 路由 | 文件 |
|---|---|
| / | index.vue |
| /people | people/index.vue |
| /about/132 | about/_id.vue |
| /any | _.vue |
| /any/woc | _.vue |
处理 404 页面,现在符合_.vue页面的逻辑
脚手架配置
nuxt.config.js
components:true // 自动在页面组件中导入components目录下的组件
预处理器配置
Nuxt - Configuration (nuxtjs.org)
全局 css 变量
yarn add @nuxtjs/style-resources
buildModules: [
'@nuxtjs/style-resources',
],
styleResources: {
less:['~/assets/less/xxx.less'] // 全局变量
}
配置404页面
在 layouts 目录下创建 error.vue 文件即可匹配不存在的路由
配置 Loading 组件
components/LoadingBar.vue
<template lang="html">
<div class="loading-page" v-if="loading">
<p>Loading...</p>
</div>
</template>
<script>
export default {
data: () => ({
loading: false
}),
methods: {
start() {
this.loading = true
},
finish() {
this.loading = false
}
}
}
</script>
<style scoped>
.loading-page {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.8);
text-align: center;
padding-top: 200px;
font-size: 30px;
font-family: sans-serif;
}
</style>
nuxt.config.js
export default {
loading: '~/components/LoadingBar.vue'
}
pages/index.js 使用
<template>
<div id="box">
<h1>index首页</h1>
</div>
</template>
<script>
export default {
mounted() {
// DOM 更新完毕之后调用 loading
this.$nextTick(() => {
// 调用 loading 中的方法,记得在 nux.config.js 里面配置
this.$nuxt.$loading.start();
setTimeout(() => this.$nuxt.$loading.finish(), 3000);
});
},
};
</script>
<style>
html,
body {
height: 100%;
}
#box {
width: 800px;
height: 500px;
margin: 0 auto;
border: 1px solid;
text-align: center;
}
</style>
asyncData 处理异步数据
此方法会在服务端获取并渲染数据
asyncData 方法会在组件(pages组件)每次加载之前被调用
在这个方法被调用的时候,第一个参数被设定为当前页面的上下文对象,你可以利用 asyncData方法来获取数据并返回给当前组件。
<template>
<div>
<h1>{{ip}}</h1>
<ul>
<li v-for="item in data" :key="item.id">{{item.title}}</li>
</ul>
</div>
</ul>
</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
async asyncData(context) {
const ip = await context.$axios.$get('http://icanhazip.com')
const { data } = await context.$axios.$get('https://cnodejs.org/api/v1/topics', {
params: {
page: 1,
tab: 'ask',
},
})
console.log(context)
// console.log(data)
return { ip,data }
},
})
</script>
<style lang="scss">
$red: red;
div {
color: $red;
}
</style>
fetch 处理数据
fetch 方法用于在渲染页面前填充应用的状态树(store)数据, 与 asyncData 方法类似,不同的是它不会设置组件的数据。
如果页面组件设置了 fetch 方法,它会在组件每次加载前被调用(在服务端或切换至目标路由之前)。
开发中会经常调用接口,返回的数据需要公共使用,需要把数据存到vuex上,可以在fetch中完成
:::warning
您无法在内部使用this获取组件实例,fetch是在组件初始化之前被调用
:::
pages/index.vue
<template>
<div class="box">
<h1>index</h1>
<nuxt-link to="/about">about</nuxt-link>
<ul v-for="item in list">
<li>{{ item.title }}</li>
</ul>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
async fetch({ $axios, store }) {
let {
data: { data: list }
} = await $axios.get("https://cnodejs.org/api/v1/topics");
store.commit("update", list);
},
computed: {
...mapState(["list"])
}
};
</script>
store/index.js
export const state = function() {
return {
list: [],
test: "123"
};
};
export const mutations = {
update(state, payload) {
state.list = payload;
}
};
动画 transition
Nuxt.js 使用 Vue.js 的 transition组件来实现路由切换时的过渡动效。默认类目是.page开通
过渡类名
.page-enter, .page-leave-to { }
.page-enter-active, .page-leave-active { }
layouts/default.vue
<<template>
<div class="box">
<nuxt-link to="/">home</nuxt-link>
<nuxt-link to="/about">about</nuxt-link>
<nuxt-link to="/user">user</nuxt-link>
<Nuxt></Nuxt>
</div>
</template>
<style>
html,body{
text-align: center;
}
a{
padding: 0 20px;
}
.page-enter, .page-leave-to {
opacity: 0;
}
.page-enter-active, .page-leave-active {
transition: opacity 0.8s;
}
</style>
动画类名
- 使用transtion属性在页面组件指定自定义动画名称
- 在布局文件
xxx-enter-active,xxx-leave-active { }
layouts/default.vue
<<template>
<div class="box">
<nuxt-link to="/">home</nuxt-link>
<nuxt-link to="/about">about</nuxt-link>
<nuxt-link to="/user">user</nuxt-link>
<Nuxt></Nuxt>
</div>
</template>
<script>
export default {
}
</script>
<style>
html,body{
text-align: center;
}
a{
padding: 0 20px;
}
.page-enter, .page-leave-to {
opacity: 0;
}
.page-enter-active, .page-leave-active {
transition: opacity 0.8s;
}
.bounce-enter-active {
animation: bounce-in 0.8s;
}
.bounce-leave-active {
animation: bounce-out 0.5s;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.25);
}
100% {
transform: scale(1);
}
}
@keyframes bounce-out {
0% {
transform: scale(1);
}
50% {
transform: scale(1.25);
}
100% {
transform: scale(0);
}
}
</style>
pages/user.vue
<template>
<h1>hi user</h1>
</template>
<script>
export default {
// 使用动画
transition: "bounce"
}
</script>
API: transition 属性 - NuxtJS | Nuxt.js 中文网
中间件 middleware
- 中间件就是一个函数, 运行在客户端或者服务端
- 项目启动或者刷新页面,运行在服务端
- 切换路由,运行在客户端
- process.server / process.static 判断执行环境
执行顺序:
-
nuxt.config.js
-
布局文件(layouts)
-
页面文件(pages)
-
全局中间件, 整个项目都可以使用
-
middleware目录下xxx.js
-
nuxt.config.js 全局注册
router :{ middleware:'全局中间件名称' } -
整个项目路由切换时或者刷新页面时,全局中间件都会执行
-
全局中间件
nuxt.config.js
export default {
router:{
middleware:"index1"
}
}
middleware/index1.js
export default () => {
console.log("这里是中间件")
}
局部中间件
2.布局中的中间件
- middleware目录下xxx.js
middleware/pagesMiddleware.js
export default () => {
console.log("pages 中间件")
}
- 在布局文件中使用middleware:'中间件名称'
layouts/default.vue
<template>
<div>
<h1>这里是 layouts</h1>
<Nuxt></Nuxt>
</div>
</template>
<script>
export default {
// 使用中间件
middleware:"layoutsMiddleware"
}
</script>
3.页面中的中间件
- middleware目录下xxx.js
- 在页面文件中使用middleware:'中间件名称'
使用方法同上面
插件 plugins
插件的注册
插件需要在 nuxt.config.js 内的 plugins 内进行注册才可以调用
nuxt.config.js
export default {
plugins:[
"插件的目录/xxx.js",
{ src:"插件的路径/xx.js"}
]
}
默认插件
- 就是一个plugins/js文件
- 项目启动时/根目录刷新, 会在客户端和服务端都执行一次,此时要注意区分环境
- 路由切换时,该插件不执行
客户端/服务端插件
nuxt.config.js
plugins:[
'~/plugins/xxx.js' // 两端都会执行
{src:'~/plugins/xxx.js', mode:'client'} // 客户端
{src:'~/plugins/xx.js', mode:'server'} // 服务端
{src:'~/plugins/xx.js', mode:'both'} // 两端
]
插件注入
注入 vue 实例
plugins/test.js
import vue from "vue";
export default function () {
// 注入 vue 原型
vue.prototype.$test = "这里是test"
console.log("插件执行了")
}
pages/index.vue
<script>
export default {
created() {
console.log(this.$test) // "这里是test"
}
}
</script>
注入 context 实例
在asyncData和fetch中可以获取到 context
plugins/client.js
export default (context) => {
context.app.myClient = "这里是客户端"
console.log("指定在客户端运行")
}
pages/index.vue
<script>
export default {
asyncData(context){
console.log(context.app.myClient)
}
}
</script>
同时注入
如果您需要同时在context,Vue实例,甚至Vuex中同时注入,您可以使用inject方法,它是 plugin 导出函数的第二个参数。将内容注入 Vue 实例的方式与在 Vue 应用程序中进行注入类似。系统会自动将$添加到方法名的前面。
plugins/test.js
export default function (context,inject) {
inject('myServer',() => {
return "这里是 server"
})
console.log("插件执行了")
}
pages/index.vue
<script>
export default {
created(context) {
let str=this.$myServer()
console.log(str) //这里是 server
},
asyncData(context){
console.log(context.app.$myServer()) //这里是 server
}
}
</script>
vuex 的使用
nuxt 内置了 vuex 所以使用的时候不需要再次安装 vuex
Nuxt.js 会尝试找到 src 目录(默认是应用根目录)下的 store 目录,如果该目录存在,它将做以下的事情:
- 引用
vuex模块 - 将
vuex模块 加到 vendors 构建配置中去 - 设置
Vue根实例的store配置项
Nuxt.js 支持两种使用 store 的方式,你可以择一使用:
- 模块方式:
store目录下的每个.js文件会被转换成为状态树指定命名的子模块 (当然,index是根模块) - Classic(不建议使用):
store/index.js返回创建 Vuex.Store 实例的方法。
一个案例体验在 nuxt 中使用 vuex
pages/index.vue
<template>
<div id="box">
<h1>index首页</h1>
<h2>{{ $store.state.count }}</h2>
<button @click="add">同步增加</button>
<button @click="asyncAdd">异步增加</button>
</div>
</template>
<script>
export default {
methods: {
add() {
this.$store.commit("add", 100)
},
asyncAdd() {
this.$store.dispatch("asyncAdd", 200)
}
}
};
</script>
<style>
html, body {
height: 100%;
}
#box {
width: 800px;
height: 500px;
margin: 0 auto;
border: 1px solid;
text-align: center;
}
</style>
store/index.js
export const state = function () {
return {
count: 0
}
}
export const mutations = {
add(state, params) {
state.count += params
}
}
export const actions = {
asyncAdd({commit}, payload) {
commit("add", payload)
}
}
模块的使用 module
您可以将模块文件分解为单独的文件:state.js,actions.js,mutations.js和getters.js。如果您使用index.js来维护state,getters,actions和mutations,同时具有单个单独的操作文件,那么仍然可以正确识别该文件。
如果在 store 里面写 xxx.js 那么记住导出 state 等以便外部调用,多个导出的 xxx.js 文件会被识别成为模块
案例
pages/about.vue
<template>
<div class="about">
<h1>随机数: {{ random }}</h1>
<h2>把随机数添加到数组内</h2>
<div>{{ $store.state.modulesList.list }}</div>
<!-- 调用 moduleA 里面的数据 -->
<div>{{ $store.state.moduleA.str }}</div>
<button type="submit" @click="addList">把随机数添加到 modulesList 模块的 list 内</button>
</div>
</template>
<script>
export default {
data() {
return {
random: 0
}
},
methods: {
getRandom() {
let random = Math.floor(Math.random() * 10 - 0 + 0);
return random;
},
addList() {
let random = this.getRandom();
this.random = random;
// 调用 modulesList 模块里面的 addList 方法
this.$store.commit("modulesList/addList",random)
}
}
}
</script>
store/modulesList.js
// 直接导出 nuxt 会识别 modulesLIst 为模块
export const state = function () {
return {
list: [1, 2, 3, 4]
}
}
export const mutations = {
addList(state, payload) {
state.list.push(payload)
}
}