nuxt 快速入门
说明
本教程根据YouTube的大神Vue Mastery的教程《Scaling Vue with Nuxt.js》;有兴趣可以油管搜索一下,b站有人搬运,并且带上了机翻,还可以
建议配合食用,毕竟,这个是我个人的笔记。
简介
nuxt是一个vue的一个类似cli框架的东西,用于处理vue无法做到的后端渲染,简单点来说就是vue的写法,服务端进行渲染成静态网页。
目前有三个这种后端渲染框架:next、nuxt、nest;分别对应目前前端三种框架:React、Vue、Angular,作为国内的话,可能国内nuxt是用的比较多的,虽然这是一个由国外开发者发布的一个工具。
静态的页面优势就是在于seo的优化,在三款后端渲染框架里面,nuxt的seo得分是最高的,但是同时,性能也是最差的,但是差也没办法,没得选。233
创建一个nuxt项目
首先如果想使用npx命令,那么npm的版本不能低于5.2.0,之后的新版本,安装npm的同时自带了npx,当然,我们这里将会使用yarn,毕竟,这是一个非常好的包管理器。
//npx
npx create-nuxt-app <项目名>
//yarn
yarn create nuxt-app <项目名>
创建项目是会让你选择你的后端渲染框架,koa这些,前端渲染框架也有,目前没用到,可以直接选择none,然后就是一堆其他设置,下面是我的一些选择
完毕后我们通过vscode打开这个创建的项目目录,运行脚本dev,此时会提示你是否愿意提交匿名数据给nuxt,可以y也可以n,自己选择,回车后nuxt就会运行项目,然后会出现一条localhost地址段,我们复制从浏览器打开,一个简单的nuxt项目就创建完成了。
目录结构理解
打开项目可以看到如下的目录结构,nuxt给我们预设好了目录结构,我们只需要在正确的目录下书写正确的代码,就能快速生成静态网页。
- components目录 --- 用于存放vue的组件目录
- layouts目录 --- 用于存放页面布局文件
- pages目录 --- 顶级视图的页面目录,每个vue文件都对应生成相应的html文件
- store目录 --- vuex
- static目录 --- 静态文件,roboot.txt或者favicon
- assets目录 --- 也是存放文件,一般为stylus,sass,images,fonts
- plugins目录 --- 用于存放一些js的库或方法
- middleware目录 --- 中间件目录
- nuxt.config.js --- nuxt的配置文件
如果需要在vue文件中引用assets中的logo.png文件,可以在地址前加上波浪号:~
<img src="~/assets/logo.png"/>
nuxt使用了vue-loader,file-loader,url-loader解析器,所以,当图片文件大于1kb时,文件名会被转为hash名,hash名的好处是,如果图片名相同,而文件已经发生了变化,对应的hash值也会发生变化,那么浏览器就会重新加载该图片,如果只使用图片名的话,名字相同,并不会触发浏览器的更新。
<img src="~/assets/sadaghgqz.png"/>
大概是这么一个意思。
如果图片小于1kb,那么就会转为base64,这个就vue项目常用的一个功能了。用于节省下载请求数。
nuxt有很多智能的设置,如路由,我们不需要手动编写路由,nuxt会自动扫描pages目录,然后自动生成对应路由结构。
简单的修改
在pages目录下创建一个vue文件,取名:create.vue
,内容如下:
<template>
<div class="create">
<h1>新建页面</h1>
</div>
</template>
修改pages目录下的index.vue文件,改为如下:
<template>
<div class="container">
<h1>首页</h1>
</div>
</template>
<script>
export default {};
</script>
<style>
</style>
在components目录下删除logo组件,新增一个NavBar.vue
文件,内容如下:
<template>
<div class="navbar">
<nuxt-link to="/">首页logo</nuxt-link>
<nav>
<nuxt-link to="/">首页</nuxt-link>
<nuxt-link to="/create">新建页面</nuxt-link>
</nav>
</div>
</template>
然后在layouts目录下,修改默认布局文件default.vue
,内容如下:
<template>
<div>
<NavBar />
<Nuxt />
</div>
</template>
<script>
import NavBar from "~/components/NavBar";
export default {
components: {
NavBar,
},
};
</script>
<style>
html {
box-sizing: border-box;
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
}
</style>
保存后可能会报错,建议修改下eslint的配置文件,打开项目根目录下的.eslintrc.js
文件,在里面的rules对象中填写以下内容:
"prettier/prettier": "off",
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-undef": "off",
"eol-last": "off",
"singleQuote": "off",
"semi": "off",
"trailingComma": "off",
"quotes": "off",
"semi": "off",
"comma-dangle": "off",
"space-before-function-paren": "off",
'vue/singleline-html-element-content-newline': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/mustache-interpolation-spacing': 'off',
保存后,在从浏览器查看,可以看到如下效果:
点击首页和新建页面,可以进行路由跳转。
目前,一个简单的修改就完成了。
如果你想访问create.vue页面,大概是这样的一条地址段:
http://localhost:3000/create
nuxt的加载模式
单页应用的加载,往往首次加载是非常缓慢的,因为浏览器下载的html文件里面没有什么可展示的内容,所有的内容都需要通过js渲染出来,所以,在js文件没有下载下来之前,页面都是一直作为空白显示。
而nuxt会现在后端生成有内容的html文件,这个文件返回给浏览器渲染后,用户已经可以预览到了,然后再加载js文件,再初始化页面,最终成为一个单页应用。
预加载模式
nuxt有一个预加载模式,他会在你滚动到这个导航菜单时,开始下载导航菜单对应的页面js文件,然后当你点击导航时,文件差不多下载完成了,就可以很快的进入到对应的路由页。
快捷脚本
nuxt初始化后有几个快捷脚本指令,大概意思如下:
- dev --- 本地运行,开发用
- build --- 打包
- start --- 启动服务
一般流程是,先build,然后start运行服务。这样网站就可以看了。开发就直接运行dev
SEO
vue插件中有一个用于seo的插件:vue-meta
;有的人可能已经用过了,有的可能不太了解,无所谓,nuxt中用法和插件用法略有不同,而且nuxt内置了vue-meta,所以无需再手动安装。
每个页面独立设置
打开index.vue文件,我们改动如下:
<template>
<div class="container">
<h1>首页</h1>
</div>
</template>
<script>
export default {
head() {
return {
title: "NUXT - 我是首页",
meta: [
{
hid: "description",
name: "description",
content: "我是首页的简介",
},
],
};
},
};
</script>
<style>
</style>
有一个head方法,return出一个对象,对象里面是键值对,这个head可以理解为html的head,他的里面有很多标签,seo中常用的就是title,meta标签,因为meta可以多个,所以他是一个数组。
meta数组中存放着一个个对象,其中有三个属性,name对应meta的name属性,content对应meta的content属性,hid则是唯一的id,为什么需要一个唯一的id,原因就是多个页面,页面之间肯定会有相同的meta标签,但是内容不同,加上nuxt加载完毕后本质上还是spa单页app,所以当路由页切换时,我们需要动态变化内容,既然要动态变化,那么一定要能拿到dom元素才行,hid就是作为获取dom元素的钥匙,所以,如果页面之间是description内容发生变化,那么两个页面的hid值也是要相同的。
使用该方式我们可以给每个页面都单独设置head,但是会有一个麻烦,因为有一部分内容是重复书写的,比如标题的一部分内容,前缀啥的,meta的标签,我们也可以设置一些默认值。
模板模式
使用模板可以减少重复书写代码,我们可以通过修改页面所所用的layout布局文件达到这个效果。
打开layouts中的default.vue文件,内容改动如下:
<template>
<div>
<NavBar />
<div class="content">
<Nuxt />
</div>
</div>
</template>
<script>
import NavBar from "~/components/NavBar";
export default {
head() {
return {
titleTemplate: "NUXT - %s",
meta: [
{
hid: "description",
name: "description",
content: "我是通用的简介",
},
],
};
},
components: {
NavBar,
},
};
</script>
title我们使用模板,所以他的key值由title改为titleTemplate,他接收一个关键字%s
,表示其他页面设置的title内容,然后最终拼接成一个标题。
index.vue中改动一下标题:
head() {
return {
title: "我是首页",
meta: [
{
hid: "description",
name: "description",
content: "我是首页的简介",
},
],
};
},
此时页面显示的内容就会根据预设的模板进行变化,title进行拼接,description则有就替换,没有就使用layout预设的值。
注意:
乍一看这个功能好像是js生成的,那么如果我们禁用js,页面的meta和title还会改变吗?
答案:会
因为当我们直接请求页面是,nuxt会现在后端生成html文件,此时html文件的一些内容就已经渲染好了,所以,即便你禁用了浏览器的js,但实际上这个功能依旧有效。比较用户手再长你也不能跑到后端来指手画脚。
其他
hid事实上并不是一个必填的选项,他是值用于更新同一个标签值所使用的,所以,在一些不需要动态变化的标签,或者预设的标签,可以不用这个hid
export default {
head: {
titleTemplate: '%s - Nuxt.js',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
// hid is used as unique identifier. Do not use `vmid` for it as it will not work
{ hid: 'description', name: 'description', content: 'Meta description' }
]
}
}
这是官方的一个例子。
路由
四个问题:
- nuxt生成了什么样的路由结构
- 如何进行嵌套路由
- 如何使用动态路由参数
- 如何替换默认的错误页
nuxt生成了什么样的路由结构
事实上nuxt生成的路由并不是不可见的,我们可以展开.nuxt
目录,旗下有一个router.js
文件,这个文件就是nuxt生成的路由,我们可以通过查看这个路由文件来调整代码。
我截取其中路由结构代码展示:
routes: [{
path: "/create",
component: _5246d105,
name: "create"
}, {
path: "/",
component: _437014d9,
name: "index"
}],
如何进行嵌套路由
如果我们想生成这样的结构:/event/create
只需要创建一个event
文件夹,然后将create.vue
文件拖放到该文件夹即可。
我们还可以在event
文件夹创建index.vue
文件,这个文件将在路径为/event
时使用,也就是所谓的默认路由页了。
如何使用动态路由参数
vue中动态路由路径如下:path:"/event/:id"
如果我们想得到这个效果,可以创建一个_id.vue
文件存放于event目录下,在nuxt中下划线代表了动态路由
_id.vue
<template>
<div>
我的动态路由参数:{{id}}
</div>
</template>
<script>
export default {
head() {
return {
title: "我是动态路由页标题",
meta: [
{
hid: "description",
name: "description",
content: "我是动态路由页的简介",
},
],
};
},
computed: {
id() {
return this.$route.params.id;
},
},
};
</script>
这样就行了,如果我们想更复杂一点,路径如下:
{path: "/event/:id/create"}
动态路由参数是路径的一部分,这样做的话,我们只需要创建一个_id
的文件夹即可,然后把create.vue
文件移入该目录即可。
如何替换默认的错误页
替换默认的错误页,需要在layouts文件夹中创建一个error.vue
文件。
这个文件的内容,我们可以参考官方的文件,路径在:.nuxt/components/nuxt-error.vue
文件。
主要内容是:props、computed
props接收一个错误对象,computed里面有两个值,一个是网页状态码,一个错误的信息,其他的内容就可以自定义了。
注意:如果我们没有指定错误页所使用的模板,默认会使用layouts中的default.vue模板。
error.vue
<template>
<div class="error">
自定义错误页:{{message}}
</div>
</template>
<script>
export default {
props: {
error: {
type: Object,
default: null,
},
},
head() {
return {
title: this.message,
meta: [
{
name: "viewport",
content: "width=device-width,initial-scale=1.0,minimum-scale=1.0",
},
],
};
},
computed: {
statusCode() {
return (this.error && this.error.statusCode) || 500;
},
message() {
return this.error.message || "Error";
},
},
};
</script>
大概是这么一个效果
事实上掘金网站也是用的nuxt,所以他的错误效果也是一样的布局
axios请求api
由于我们在初始化的项目的时候就已经选择了axios,所以我们不需要再手动安装axios库,那么如何使用axios呢?
注意:
如果初始化的时候没有选择axios,可以使用安装命令安装一下
yarn add @nuxtjs/axios
在此之前我们要明白nuxt是怎么请求api的。
api使用流程:
- 普通情况下,用户访问网页,浏览器发出请求,后端服务器找到对应的页面进行渲染,在渲染前会触发api请求,只有当api请求完毕后,得到数据,再将网页渲染好,返回给浏览器,浏览器渲染给用户看
- 当用户已经进入的页面后,进入下一个页面,此时api请求则是由浏览器发出。服务器将内容返回给浏览器,浏览器进行渲染,这里就是spa的效果范围了。
那么nuxt是怎么判断api是否存在,或者怎么去调用api呢?
nuxt在vue的生命周期中增加了很多钩子,其中就有一个名为asyncData(){}
的生命周期。
我们将不再传统的created
生命周期中做api请求,而是在asyncData
中,并且该周期需要return出一个对象,这个对象最终会和vue上下文中的data对象合并,也就说,api获取的数据可以直接调用data的属性一样使用。
<template>
<div class="container">
<h1>首页</h1>
{{apiData}}
</div>
</template>
<script>
export default {
asyncData({ $axios }) {
return $axios.get("http://localhost:3000/db").then((res) => {
return {
apiData: res.data,
};
});
},
head() {
return {
title: "我是首页",
meta: [
{
hid: "description",
name: "description",
content: "我是首页的简介",
},
],
};
},
};
</script>
<style>
</style>
asyncData接收一个参数,这个参数里面常用的属性有:$axios,params,error,还有一些其他属性,常用就这三种。
通过$axios的请求,then回调处理,然后return出一个键值对对象,最终这个对象会和vue上下文的data属性合并,我们就可以直接调用apiData属性。
唯一需要注意的是一定要把整个axios请求先return出去,nuxt肯定是监听他的promise状态的。
效果图:
这里就不多说怎么做后端api返回了,这里是快速入门,知道nuxt的整个流程就是本教程的目的。
但是,这里我们只有成功的处理,如果api请求失败呢,怎么办?
请求失败或其他错误处理
asyncData({ $axios, error }) {
return $axios
.get("http://localhost:3000/db")
.then((res) => {
return {
apiData: res.data,
};
})
.catch((err) => {
error({
statusCode: 503,
message: "服务端api请求发生错误" + err.message,
});
});
},
其实也很简单,解构出error处理函数,对axios请求进行catch捕获,如果出现错误,我们调用error函数,传入一个对象,对象有两个键值对,分别对应自定义错误页中的两个computed属性,这个key值也都是约定俗成的。
当服务api请求发生错误后,会触发error方法,最终用户看到的,是通用的error页面。
如图:
async、await,nuxt进度条
我们都知道,async和await是异步改同步的方法,当我们滥用或者不得不对promise的回调进行多重嵌套的时候,那代码简直就是噩梦,维护性极差,所以我们可以通过async和await对代码进行改造。
async asyncData({ $axios, error }) {
try {
const { data } = await $axios.get("http://localhost:3000/db");
return {
apiData: data,
};
} catch (err) {
error({
statusCode: 503,
message: "服务端api请求发生错误" + err.message,
});
}
},
这样改造后,效果其实和上面那段是一样的,不过这种写法,在多个回调嵌套时非常有用。
并且这样写逻辑上更容易读懂。
这里我们再穿插一下 asyncData
接收的参数content
中的params属性,这个属性其实就是路由的params属性,所以,我们可以通过这个属性获取到动态路由参数。
async asyncData({ params }) {
//params是路由的params属性
},
nuxt进度条
nuxt进度条效果可以参考进度条插件nprogress
,使用方式也很简单,打开nuxt.config.js
文件,添加一条属性即可。
export default {
//进度条
loading: { color: "#39b982" }
}
保存即可。
进度条用在什么情况呢? 仔细想一想就能知道,在api流程中我们知道有两种情况,第一种情况服务器是一件渲染好了html网页,那么只有第二种情况才会触发了。
比如,当用户默认进入的是首页,首页的内容由服务器进行渲染,包括api请求,当用户进入第二个路由页时,如果路由页存在api请求,此时的请求是由浏览器发出的,所以进度条要在这里触发,这个触发nuxt已经设置好了,我们只需要设置loading的属性即可。
loading的属性除了颜色,我们还能设置进度条的宽度,属性为:height
,除了这个还有很多属性设置
Key | Type | Default | Description | |
---|---|---|---|---|
color | String | 'black' | CSS color of the progress bar | |
failedColor | String | 'red' | CSS color of the progress bar when an error appended while rendering the route (if data or fetch sent back an error for example). | |
height | String | '2px' | Height of the progress bar (used in the style property of the progress bar) | |
throttle | Number | 200 | In ms, wait for the specified time before displaying the progress bar. Useful for preventing the bar from flashing. | |
duration | Number | 5000 | In ms, the maximum duration of the progress bar, Nuxt.js assumes that the route will be rendered before 5 seconds. | |
continuous | Boolean | false | Keep animating progress bar when loading takes longer than duration. | |
css | Boolean | true | Set to false to remove default progress bar styles (and add your own). | |
rtl | Boolean | false | Set the direction of the progress bar from right to left. |
这里就把官方文档的属性表搬过来了,虽然用的情况很少。
vuex
vuex的使用,一般是将api请求封装到vuex的actions中,然后vue页面直接调用vuex中对应的异步方法即可,nuxt对vuex开启了命名空间,所以在使用时注意用法。
首先我们在store文件夹创建一个events.js
文件,文件名其实可以随意取,这个没有规定,nuxt一样会自动扫描store中的文件,所以我们需要像vue cli那样,创建一个index.js文件作为vuex的入口。
events.js
import axios from "axios";
const api = axios.create({
baseURL: "http://localhost:3000"
})
export default {
state() {
return {
apiData: {},
}
},
getters: {
apiData(state) {
return state.apiData;
}
},
mutations: {
setApiData(state, value) {
state.apiData = value;
}
},
actions: {
getApiData({ commit }) {
return api.get("/db").then(res => {
commit("setApiData", res.data);
})
}
}
}
用法和平时用vuex差不多,主要是我们在这里import引入的axios,这就可以明白,我们可以像平时写项目一样,对axios进行二次封装,添加拦截器这些,然后导出,这里就不多详细写了,都是重复造轮子,没啥必要。
省的在asyncData中使用$axios束手束脚的。
我们在vue文件中就可以这样调用:
<template>
<div class="container">
<h1>首页</h1>
{{apiData}}
</div>
</template>
<script>
export default {
async fetch({ store, error }) {
try {
await store.dispatch("events/getApiData");
} catch (err) {
error({
statusCode: 503,
message: "服务端api请求发生错误" + err.message,
});
}
},
head() {
return {
title: "我是首页",
meta: [
{
hid: "description",
name: "description",
content: "我是首页的简介",
},
],
};
},
computed: {
apiData() {
return this.$store.getters["events/apiData"];
},
},
};
</script>
<style>
</style>
注意我们是在fetch钩子中,因为我们不需要和上下文data进行合并,所以我们需要将异步的方法写在fetch钩子上,fetch钩子本身就是用于处理异步函数的,并且在fetch中可以解构到store。
使用vuex请求的方式,在fetch中统一使用,进度条是依旧是有效的。
部署nuxt
之前我们了解过两个脚本,build和start,使用这两个脚本,我们将了解到nuxt的动态生成并部署的方式。
在部署之前我们要明白,nuxt底层上还是node的,所以部署的过程无非是这么一个流程,我们node运行这个项目,拿到本地的地址,localhost+端口号,然后如果需要绑定域名的话,我们需要通过反代的形式。
动态的部署
动态的部署,用户每次请求一个url地址,服务器都是即时渲染一个html文件出来并返回,这就可以保证用户每次刷新访问的,都是最新的。
这种有好处也有坏处,坏处就是非常浪费资源,但是如果你无所谓的话,其实这种还挺好用的。
我们将整个项目文件上传到服务器,服务器要有node,然后有对应的包管理器,我们先进入到服务站的项目目录,安装好所有的插件。
yarn install
安装完毕后运行build
脚本:yarn build
运行完毕后我们在运行start
脚本,这将会运行nuxt后端服务:yarn start
然后会返回一段地址,反代的话就反代这个地址段,如果不用的话,可以直接用了。本地访问使用localhost,外链就是ip+端口了,这个是网站部署的基本常识,不用再啰嗦一堆了。
如果文件发生了修改,还是需要重新build然后start,省点事我们可以两个命令合二为一:yarn build start
静态文件部署
如果你的文件不需要即时更新,那么可以先生成静态的html文件,然后用户每次访问直接拿对应的文件就行了,这种好处就是静态化,资源不会很浪费,但是也有缺点,如果你的文件发生了变化,那么就要重新生成一次。
我们运行nuxt提供的另一个快捷脚本generate
,该脚本会在项目内生成dist目录,这个就和vue cli打包一样了,yarn generate
然后我们直接使用nginx或者其他程序来搭建网站服务,目录就指向这个dist文件夹。
注意:
如果存在动态路由页面,用户从首页进来是没有问题的,但是再刷新就会报错,因为这个动态页面,nuxt默认是不能直接生成的,动态动态,参数是不确定的,所以nuxt没法直接生成。
如果要生成,我们需要预设参数。
打开nuxt.config.js
文件,在里面添加如下配置:
假设动态路径为:/events/:id
export default {
generate: {
routes: ["/events/one","/events/one"]
}
}
我们手动配置好routes,再运行yarn generate
即可生成对应的静态页面。
当然每次手动写都很麻烦,routes可以为一个函数,最终return出一个数组即可,所以我们可以在函数里面进行复杂的操作。
export default {
generate: {
routes() {
return ["/events/one","/events/one"];
}
}
}
以上就是关于nuxt的快速入门教程。
额外知识
使用generate静态的文件,如果想要本地测试,可以使用一个插件:http-server
http-server
是一个很简单的服务器程序,运行代码:http-server dist
如果你没有安装可以先安装:yarn add http-server
这个程序也可以用于vue cli打包后的文件,因为vue打包后的html文件,如果没有额外配置,直接打开是空白的,这是路径的问题。
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿站点。未经许可,禁止转载。
全部评论 1
呵呵哒
Google Chrome Windows 10