概述
前端领域中,随着Vue,React,Angular等框架的流行,前端工程化、模块化成为了当下主流技术方案。这类框架构建的SPA单页应用具有用户体验好、渲染性能好、可维护性高等有点,但是也存在以下两个方面的问题:
- 首屏加载时间长
SPA应用采用客户端渲染,用户需要等待客户端js解析完成之后才能看到页面,这样会导致首屏加载时间变长,用户体验差。
- 不利于SEO
由于SPA应用采用客户端渲染,在js未完成解析之前,网站HTML是没有内容的,这样导致搜索引擎爬取站点HTML时获取不到内容。
为了解决这两个问题,业界提出了一种新的解决方案,如下图:
利用服务端渲染解决首屏加载慢和不利于SEO的缺陷,首屏渲染完成之后,客户端渲染接管页面重新成为单页应用以保证良好的用户体验。这种方式称为现代化服务端渲染方式或者同构渲染。
与传统服务端渲染区别
传统服务端渲染,如JSP可以总结为以下几步:
- 客户端发送请求
- 服务端根据请求查找模板并获取数据。
- 执行渲染
- 生成html返回客户端展示
这种传统服务端渲染方式,会存在以下缺点:
- 前后端完全耦合,不利于开发维护。
- 前端发挥空间小。
- 服务端压力大。
- 用户体验一般。
而同构渲染只是在首屏渲染的时候和传统服务端类似,都是返回渲染好的html页面,但是,同构渲染中,当客户端展示渲染好的html页面后,客户端渲染会接管页面的控制权,也就是后续的渲染都是由客户端进行的,这样可以保证良好的用户体验。
同构渲染缺点
与单页应用相比,由于首屏渲染采用的是服务端渲染,所以存在以下缺点:
- 开发条件有限,开发有限制(服务端只能使用nodejs,而且并不是所有的工具包都能在服务端渲染中使用)。
- 涉及构建和部署的要求高(需要部署客户端和服务端,不能再和单页应用一样,部署静态站点即可)。
- 更多的服务端负载。
Nuxt.js
Nuxt.js是基于vue技术栈的一种同构应用解决方案,它屏蔽了vuejs构建同构应用的难点。
基本使用
搭建nuxtjs同构应用相对比较简单,通过以下四步即可搭建最简单的同构应用:
- 创建项目文件夹。
- npm或者yarn安装nuxt。
- 添加pages文件夹并在其中添加index.vue文件。
- 执行npx nuxt。
基础路由
pages文件夹下存放所有的路由页面vue文件,nuxt会根据pages文件夹自动生成路由。
如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在解析路由结构时,如果遇到以_为前缀的vue文件时,会将其定义为带参数的动态路由。
如下面的文件结构:
pages/
--| _slug/
-----| comments.vue
-----| index.vue
--| users/
-----| _id.vue
--| index.vue
生成的路由表为:
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'
}
]
}
嵌套路由
在vuejs中,我们可以通过子路由实现路由的多层嵌套,同样在nuxtjs中,可以通过特殊的文件结构实现嵌套路由。
创建嵌套路由,需要添加一个Vue文件,同时添加一个与该文件名称相同的文件夹用于存放子路由组件(在Vue文件中需要添加类似route-view的nuxt-child节点。)
如下面的文件结构:
pages/
--| users/
-----| _id.vue
-----| index.vue
--| users.vue
生成的路由表如下:
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'
}
]
}
]
}
自定义路由
nuxtjs不仅可以通过pages文件夹结构自动生成路由表,同样提供了配置文件的方式配置自定义路由。
nuxtjs的配置文件和vue.config.js一样,在项目根目录下创建nuxt.config.js文件,此文件默认导出一个配置对象,配置对象有一个router
属性,可以在此属性下配置自定义路由。
export default {
router: {
extendRoutes(routes, resolve) {
// 可以采用此方式清空默认生成的路由
// routes.splice(0)
routes.push(...[
{
path: '/',
component: resolve(__dirname, 'your.vue'),
}
])
}
}
}
模板
nuxtjs是基于html模板生成html文件的,我们可以在项目中修改此模板。
定制模板需要在项目根目录下创建app.html文件,默认的html结构如下:
<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
<head {{ HEAD_ATTRS }}>
{{ HEAD }}
</head>
<body {{ BODY_ATTRS }}>
{{ APP }}
</body>
</html>
我们可以在head中添加相应自定义的css、js引用。
asyncData
在vuejs项目中,某个组件如果想要从服务端获取数据,一般都会在created声明周期函数中发起请求。在同构应用中,如果想要页面内容能够SEO,那么就不能采用此方式。
在nuxt中为vue组件添加了asyncData方法,此方法会在服务端被执行,执行后的结果会被合并到当前组件的data对象中。
如下例,在服务端获取所有的文章和标签并渲染:
async asyncData() {
const [articleRes, tagRes] = await Promise.all([
getAllArticles({
offest: 0,
limit: 10,
}),
getAllTags(),
])
const { articles, articlesCount } = articleRes.data
const { tags } = tagRes.data
return {
articles,
articlesCount,
tags,
}
}
需要注意的是,此方法虽然是vue组件的一个方法,但是由于其在服务端被调用,所以方法内部不能通过this对象获取vue实例,如果想要获取实例上的某些信息,可以用上下文对象。
上下文对象
asyncData方法包含一个参数context,context对象上包含路由等常用信息。
route: 当前路由对象
store: store对象
params, query: 路由参数
req, res: 请求,响应对象
redirect: 用于重定向
身份验证
在同构应用中,当用户登录之后,其身份信息需要在客户端和服务端都能够被获取到,因此可以将信息保存在cookie中。
客户端获取信息并存入cookie:
methods: {
onSubmit() {
let request = this.isLogin ? login : register
request(this.user)
.then(({ data }) => {
// 获取到用户信息
const { user } = data
if (user) {
const Cookie = process.client ? require('js-cookie') : undefined
// 将获取到的用户信息保存在cookie中
Cookie.set('user', user)
this.$store.commit('setUser', user)
}
})
.catch((err) => {
})
}
}
服务端读取cookie并存入store供客户端使用:
在store的action中可以添加一个名称为nuxtServerInit
的异步方法,该方法会在nuxt应用启动的时候被调用。
nuxtServerInit({ commit }, { req }) {
let user = null
if (req && req.headers.cookie) {
const cookieParser = process.server ? require('cookieparser') : undefined;
const parsed = cookieParser.parse(req.headers.cookie)
try {
user = JSON.parse(parsed.user)
} catch (err) {
}
}
commit('setUser', user)
}
插件
如果想要在vuejs程序运行之前执行某些js操作,如注册第三方组件库,此时可以利用插件。
所有的插件全部放在plugins目录下,如果想要在nuxt应用中使用elementui组件库,可以在plugins文件夹下添加element-ui.js文件:
import Vue from 'vue'
import Element from 'element-ui'
import locale from 'element-ui/lib/locale/lang/en'
Vue.use(Element, { locale })
然后在nuxt.config.js的plugins属性数组中添加相应文件路径:
plugins: [
'@/plugins/element-ui',
]
之后,就可以在vue组件中正常使用element-ui。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。