Vue的服务端渲染
Posted 前端纸飞机
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue的服务端渲染相关的知识,希望对你有一定的参考价值。
于你而言,我不过是黄粱一梦,于我而言,你却是我余生所求。
什么是服务端渲染?
?传统的web开发体验
通过express来写一个案例
npm init -y
npm i express -S
const express = require('express')
const app = express()
app.get('/',function(req,res)
res.send(`
<html>
<body>
<div id="app">
<h1>学习吧</h1>
<p class="demo">学习吧还不错</p>
</div>
</body>
</html>
`)
)
app.listen(3000,()=>
console.log(`服务运行在http://localhost:3000`)
)
检验方法:打开页面,查看源码
?spa时代
到了vue,react时代,单页面应用优秀的用户体验,逐渐成为了主流,页面整体是js渲染出来的,称之为客户端渲染
检验方法:打开页面,查看源码
单页面缺点(主要有两个):
首屏渲染性能:必须得等到js加载完毕,才能渲染出首屏
seo:爬虫只能拿到一个id为app的div,会认为页面是空的,不利于div
为了解决上面的两个问题,于是出现了SSR
?ssr
后端渲染出完整的首屏的dom结构返回,前端拿到的内容带上首屏,后续的页面操作,再用单页的路由跳转和渲染,称之为服务端渲染(server side render)
注意:
ssr一般只做首屏的!
ssr是在传统的web和spa之间取了折中!
ssr体验(nuxt.js)知识点
?Nuxt.js是一个基于Vue.js的通用型框架。
通过对客户端/服务端基础架构的抽象组织,Nuxt.js主要关注的是应用的UI渲染
结论:
nuxt.js不仅仅用于服务端渲染也可用于spa应用开发
利用nuxt提供的基础项目结构,异步数据加载,中间件支持,布局等特性可大幅提高开发效率
nuxt可用以网站静态化(nuxt有命令可以打包成静态文件,一般用于访问次数最多的页面,技巧:数据更新触发打包命令)
?nuxt.js特性
基于vue.js
自动代码分层
服务端渲染
强大的路由功能,支持异步数据
静态文件服务
es6/es7语法支持
打包和压缩js和css
html头部标签管理
本地开发支持热加载
集成ESlint
支持各种样式预处理器:sass,less,stylus
支持http/2推送
?nuxt渲染流程
一个完整的服务器请求到渲染流程
?nuxt安装
运行 npx create-nuxt-app 项目名
运行项目:npm run dev
目录结构:
约定于优先配置
pages目录下所有的*.vue文件生成应用的路由配置(自动生成路由配置),新建pages/users.vue
<template>
<div class="container">
用户列表
</div>
</template>
<script>
export default
</script>
<style>
</style>
会在.nuxt目录下自动配置router
打开页面: http://localhost:3000/users
路由
?导航
虽说和vue的router-link差不多,但是如果我们使用了nuxt,那么为了后期的一些优化我们推荐使用nuxt-link
index.vue里写
<nuxt-link to="/users">跳转到用户列表</nuxt-link>
?子路由和路由导航
修改pages目录结构
效果:
自动生成目录.nuxt下仔细查看router.js的内容:
结论:
name的命名:文件所在目录-文件名
改造主页的index.vue
<template>
<div class="container">
<nuxt-link to="/users">跳转到用户列表</nuxt-link>
<el-button @click="$router.push('/users')">用户列表</el-button>
<el-button @click="$router.push(name:'users-detail')">用户详情</el-button>
</div>
</template>
而我们是安装选项时集成了elementUI的
结果点击按钮可以正常跳转
?动态路由
根据上面的方法可以自动配置一个动态路由
再来改在首页的index.vue和_id.vue 以及detail.vue
//首页index.vue
<template>
<div class="container">
<nuxt-link to="/users">跳转到用户列表</nuxt-link>
<el-button @click="$router.push('/users')">用户列表</el-button>
<el-button @click="$router.push(name:'users-detail',query:id:2)">用户详情</el-button>
<el-button @click="$router.push(name:'users-id',params:id:1)">用户详情</el-button>
</div>
</template>
//_id.vue
<template>
<div class="container">
用户id: $route.params.id
</div>
</template>
//detail.vue
<template>
<div class="container">
用户id: $route.query.id
</div>
</template>
页面参数的效果(query)
路由参数的效果(params)
?嵌套路由
构造的目录结构要如下才会生成嵌套路由
目录结构如下
生成的路由配置如下
export const routerOptions =
mode: 'history',
base: decodeURI('/'),
linkActiveClass: 'nuxt-link-active',
linkExactActiveClass: 'nuxt-link-exact-active',
scrollBehavior,
routes: [
path: "/users",
component: _c8a52a2e,
children: [
path: "",
component: _228b8179,
name: "users"
,
path: "detail",
component: _7190858c,
name: "users-detail"
,
path: ":id",
component: _a064e3be,
name: "users-id"
]
,
path: "/",
component: _564390b3,
name: "index"
],
fallback: false
users.vue里要写路由展示区域nuxt.child
<template>
<div>
<nuxt-child />
</div>
</template>
如此就可以正常使用了!
?路由守卫
plugin目录下建立router.js
export default (app) =>
app.router.beforeEach((to,from,next)=>
console.log(`我要去$to.path`)
next()
)
nuxt.config.js里注册这个方法
plugins: [
'@/plugins/element-ui',
'@/plugins/router'
],
自定义布局
default.vue是会匹配所有的页面,在上面添加内容会增加到所有页面,所以如果有一个所有页面都要共享的组件就写在default.vue里
layout目录下建立users.vue,注意必须有<nuxt/> 否则会替换掉整个页面
<template>
<div>
用户页面自定义布局
<nuxt/>
</div>
</template>
再去pages下的users指定一下使用哪个自定义布局,通过layout:'自定义布局的组件名'
<template>
<div>
<nuxt-child />
</div>
</template>
<script>
export default
layout: 'users'
</script>
错误页面
其实nuxt本身自带错误页面的(优点丑,而且错了就啥都看不见),所以我们自定义一个
在layout里新建error.vue
<template>
<div class="container">
<h1 v-if="error.statusCode == 404">页面找不到</h1>
<h1 v-else>应用发生错误异常</h1>
<nuxt-link to="/">首页</nuxt-link>
</div>
</template>
<script>
export default
props:['error']
</script>
在pages下的users.vue里加上 foot.bar ,注意,这是不存在的,为了让程序出错
<template>
<div>
<nuxt-child />
foot.bar
</div>
</template>
<script>
export default
layout: 'users'
</script>
样式有点丑,可以自己修改的
页面(涵盖异步数据获取)
例如定义data数据啥的,和vue是一模一样的!!,不多说
值得注意的是,我们可以设置一些其他的信息,例如页面标题
<script>
export default
head:
title:'页面详情'
</script>
效果:
可以覆盖掉全局的seo信息
?异步数据的获取
asyncData方法使得我们可以在设置组件的数据之前就能异步获取或处理数据
asyncData//这个会在beforeCreate生命周期钩子之前执行
注意他会在客户端和服务端两端执行
在pages下的users.vue里写
<template>
<div>
<nuxt-child />
<ul>
<li v-for="user in users" :key="user.id" > user.name </li>
</ul>
</div>
</template>
<script>
function getUsers()
return new Promise(resolve=>
setTimeout(()=>//模拟异步请求
resolve([name:'tom',id:1,name:'jon',id:2])
,1500)
)
export default
layout: 'users',
async asyncData() //这个会在beforeCreate生命周期钩子之前执行
console.log(process.server); //返回true就是在服务端执行了
//使用async await
const users = await getUsers();
//return的数据会和data合并
return users ;
,
</script>
注意事项:
?上下文对象的使用:
在pages下的users下的detail.vue中
<script>
export default
head:
title:'页面详情'
,
async asyncData( query,error ) //这个会在beforeCreate生命周期钩子之前执行
console.log(process.server); //返回true就是在服务端执行了
if ( query.id )
return user: name:'tom'
error( statusCode:400,message:'请传递用户id' )
</script>
不传入id就爆了我们需要的错误
?整合axios
如何安装nuxt是没选择axios,那么我们就要整合一下了!
npm install @nuxtjs/axios -S
再在nuxt.config.js里配置,用来解决跨域的问题
modules: [
// Doc: https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
],
/*
** Axios module configuration
** See https://axios.nuxtjs.org/options
*/
axios:
proxy: true
,
proxy:
"/api": "http://localhost:3001/"
,
当然如果所有页面都启用了ssr那么自然也就不需要跨域了,但通常大家都是首屏启用了ssr,其他页面仍是单页面,所有应当配置跨域
server目录下建立api.js,定义我们的后端接口,需要安装koa,koa-router,全局安装nodemon,装了就不需要
const koa = require("koa")
const app =new koa()
const Router = require("koa-router")
const router = new Router(prefix:'/api/users') //请求地址
const users = [name:'tom',id:1,name:'jey',id:2]
router.get('/:id',ctx => //带个参数id
const user = users.find(u => u.id == ctx.params.id)
if(user)
ctx.body = ok:1,user
else
ctx.body = ok:0
)
app.use(router.routes())
app.listen(3001)
nodemon api.js来启动这个服务
_id.vue中
<template>
<div class="container">
用户id: $route.params.id
<!-- foot.bar -->
user.name
</div>
</template>
<script>
export default
async asyncData( params,$axios )
//注意返回的就是响应数据,因为是被封装过的(讲课的说的,我测试结果不是!)
const data = await $axios.get(`/api/users/$params.id`)
if(data.data.ok)
return user:data.data.user
error( statusCode:400,message:"id有误,查询失败" )
,
</script>
注意我注释掉的东西,效果如下
?拦截器实现
nuxt.config.js里配置
plugins: [
'~/plugins/axios'
],
在plugins目录下建立axios.js
//前端请求自动加上token
export default function( $axios )
//onRequest为请求拦截器帮助方法
$axios.onRequest(config =>
if(!process.server)
// config.headers.token = localStorage.getItem('token')//将来是这个操作
config.headers.token = 'jiba'
)
这样就完成了一个拦截器,效果看下图network的token
vuex
应用根目录下如果存在store目录,Nuxt.js将启用vuex状态树。
定义各状态树时具名导出state,mutations,getters,actions即可
注意:也是直接用的,不需要去配置什么,详情看下面教程
在store目录下建立index.js
//因为是具名导出,所以state必须是state,mutations必须是...
export const state = () => (
count: 0
)
export const mutations =
increment(state)
state.count ++
在store目录下建立users.js
export const state = () => (
list: []
)
export const mutations =
set(state,list)
state.list = list;
,
add(state,name)
state.list.push(name)
在pages目录下建立mine.vue
<template>
<div>
我的
<p @click="increment">计数:count</p>
<p><input type="text" placeholder="添加用户" @keyup.enter="add($event.target.value)"></p>
<ul>
<li v-for="user in list" :key="user.id">user.name</li>
</ul>
</div>
</template>
<script>
import mapState,mapMutations from 'vuex'
function getUsers() //提供一个接口
return new Promise(resolve =>
setTimeout(() =>
resolve([name:'tom',id:1,name:'jb',id:2])
,1500)
)
export default
fetch(store) //这个fetch不是请求的fetch,这是一个钩子,执行时间早于data,组件还没创建
//fetch在创建组件前执行填充状态树
//提交时注意命名空间
return getUsers().then(users => store.commit("users/set",users))//对应store的users下的set
,
computed:
...mapState(["count"]),
...mapState("users",["list"])
,
methods:
...mapMutations(["increment"]),
...mapMutations("users",["add"])
,
</script>
注意:
1.此fetch非彼fetch
2.store下的index.js是基石,store会自动生成为这个样子
3.fetch的commit后的有规则
Vue SSR 实战首屏渲染(当然比起nuxt这样很麻烦)
?先创建个vue的项目
vue create ssr
?安装vsr(vue的server渲染器)
npm install vue-server-renderer --save
?一个简单的实例,单独建立个server.js
const express = require('express')
const vue = require('vue')
const app = express()
const renderer = require('vue-server-renderer').createRenderer()
const page = new vue(
data:
name: "学习吧",
count: 1
,
template: `
<div>
<h1>name</h1>
<h1>count</h1>
</div>
`
)
app.get('/',async function(req,res)//进入这个页面会将组件转为字符串发给前端在页面
const html = await renderer.renderToString(page)
res.send(html)
)
app.listen(3000,()=>
console.log('启动成功')
)
效果会是这样
?在vue中构建步骤
首先webpack会把资源会打包成两个包,一个服务,一个客户端,用户访问服务后根据客户端知道找谁,把相应的html渲染出来
通常前端都是vue单文件组件,用vue-loader构建,所以ssr环境需要webpack怎么操作呢?下面讲解
路由vue-router
单页面应用的页面路由,都是前端控制,后端只负责提供数据
一个简单的单页应用,使用vue-router,为了方面前后端公用路由数据,我们新建router.js,对外暴露createRouter
npm i vue-router -S
在src下建立router.js
import Vue from 'vue'
import Router from 'vue-router'
import Index from './components/Index'
import Kkb from './components/Kkb'
Vue.use(Router)
export function createRouter ()
return new Router(
routes: [
path: '/',component: Index,
path: '/kkb',component: Kkb
//...
]
)
在components目录下建立Index.vue组件和Kkb.vue
//index
<template>
<div>
<h1>hi, name </h1>
</div>
</template>
<script>
export default
data()
return
name: '首页'
,
</script>
//kkb
<template>
<div>
<h1>hi, name </h1>
</div>
</template>
<script>
export default
data()
return
name: '学习吧'
,
</script>
再App.vue中使用路由
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<ul>
<li><router-link to="/">首页</router-link></li>
<li><router-link to="/kkb">学习吧</router-link></li>
</ul>
<router-view></router-view>
</div>
</template>
<script>
export default
name: 'app',
components:
</script>
<style lang="scss">
</style>
csr和ssr设置统一入口
src下建立createapp.js
import Vue from 'vue'
import App from './App.vue'
import createRouter from './router'
export function createApp (context) //context是服务器传来的请求对象
const router = createRouter()
const app = new Vue(
router,
context,
render: h => h(App)
)
return app,router
main.js里这么做
import Vue from 'vue'
import App from './App.vue'
import createApp from './createapp'
const app,router = createApp()
Vue.config.productionTip = false
router.onReady(()=>
app.mount("#app")
)
// new Vue(
// render: function (h) return h(App) ,
// ).$mount('#app')
再在src下建立entry-server.js
import createApp from './createapp'
export default context =>
//我们返回一个promise
//确保路由或者组件准备就绪
return new Promise((resolve,reject) =>
const app,router = createApp(context)
router.push(context.url)
router.onReady(()=>
resolve(app)
,reject)
)
服务端渲染,我们需要能够处理.vue组件,所以需要webpack的支持
后端加入webpack
npm install cross-env vue-server-renderer webpack-node-externals
lodash.merge --save
这讲的就是个大致的思路,用起来非常麻烦,推荐使用nuxt.js。后续还会更新,有空就更新
nuxtjs学习资料06_SSR 下载以上是关于Vue的服务端渲染的主要内容,如果未能解决你的问题,请参考以下文章