读本文前,请先熟读nuxt
官方文档,并且具备一定的vue.js
相关开发经验
中文文档
英文文档
vue SSR指南
一、CSR和SSR对比
在SPA
之前的时代,我们传统的Web
架构大都是SSR
,如:Wordpress(PHP)
、JSP
技术、JavaWeb
等这些程序都是传统典型的SSR
架构,即:服务端取出数据和模板组合生成 html
输出给前端,前端发生请求时,重新向服务端请求html
资源。
SPA(CSR):
SPA
应用,到了Vue
、React
,单页面应用优秀的用户体验,逐渐成为了主流,页面整体是javaScript
渲染出来的,称之为客户端渲染CSR
。SPA
渲染过程。由客户端访问URL
发送请求到服务端,返回HTML
结构(但是SPA
的返回的HTML
结构是非常的小的,只有一个基本的结构)。客户端接收到返回结果之后,在客户端开始渲染HTML
,渲染时执行对应javaScript
,最后渲染template
,渲染完成之后,再次向服务端发送数据请求,注意这里时数据请求,服务端返回json
格式数据。客户端接收数据,然后完成最终渲染。
CSR原理图
CSR多数是基于webpack构建的项目,编译出来的html文件,资源文件都被打包到js中,这样的页面是不利于搜索引擎优化(SEO, Search Engine Optimization)
,并且内容到达时间(time-to-content) (或称之为首屏渲染时长)
也有很大的优化空间
简单来讲,SPA
虽然给服务器减轻了压力,也存在比较明显的两个缺点:
JavaScript
加载完毕,并且执行完毕,才能渲染出首屏。SEO
不友好:爬虫只能拿到一个div
元素,认为页面是空的,不利于SEO
。什么是SEO
呢?SEO
即通过各种技术(手段)来确保,你的Web
内容被搜素引擎最大化收录,最大化提高权重,带来更多流量。大部分的搜索引擎仅能抓取URI
直接输出的数据资源,对于 Ajax
类的异步请求的数据无法抓取
因此,对于那些展示宣传型页面,如官网,必须进行服务端渲染
SSR:
为了解决如上两个问题,出现了SSR
解决方案,后端渲染出首屏的DOM
结构返回,前端拿到内容带上首屏,后续的页面操作,再用单页面路由和渲染,称之为服务端渲染(SSR)
。
SSR
渲染流程是这样的,客户端发送URL
请求到服务端,在服务端做出html
和数据
的渲染,渲染完成之后返回html
结构,客户端拿到页面的html
结构渲染首屏。所以用户在浏览首屏的时候速度会很快,因为客户端不需要再次发送ajax
请求。并不是做了SSR
我们的页面就不属于SPA
应用了,它仍然是一个独立的spa
应用。
SSR原理图
SSR
是处于CSR
与SPA
应用之间的一个折中的方案,在渲染首屏的时候在服务端做出了渲染,注意仅仅是首屏,其他页面还是需要在客户端渲染的,在服务端
接收到请求之后并且渲染出首屏页面,会携带着剩余的路由信息预留给客户端
去渲染其他路由的页面。
vueSSR
将本来要放在浏览器执行创建的组件,放到服务端先创建好,然后生成对应的html将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。
在浏览器第一次访问某个URI
资源的时候(首屏),Web
服务器根据路由拿到对应数据渲染并输出,且输出的数据中包含两部分:
在首屏渲染完成之后,此时我们看到的其实已经是一个和之前的SPA
相差无几的应用程序了,接下来我们进行的任何操作都只是客户端的应用进行交互,页面/组件由Web
端渲染,路由也由浏览器控制,用户只需要和当前浏览器内的应用打交道就可以了。
vueSSR原理图
webpack
将 Source
打包出两个bundle
文件:其中 Server Bundle
用于服务端渲染,服务端通过渲染器 bundleRenderer
将 bundle
生成首屏html
片段;而 Client Bundle
用于客户端渲染,首屏外的交互和数据处理还是需要浏览器执行 Client Bundle
来完成
缺点:
(lifecycle hook)
中使用;一些外部扩展库(external library)
可能需要特殊处理,才能在服务器渲染应用程序中运行。Node.js
中渲染完整的应用程序,显然会比仅仅提供静态文件的 server
更加大量占用 CPU
资源,因此如果你预料在高流量环境(high traffic)
下使用,请准备相应的服务器负载,并明智地采用缓存策略。二、nuxt.js介绍
1. nuxt.js是什么?
Nuxt.js
是vue
官方推荐的一个基于Vue.js
的做Vue SSR
的通用应用框架(开箱即用),集成了Vue,Vue-Router,Vuex,Vue-Meta
等组件/框架,内置了webpack
用于自动化构建,使我们可以更快速地搭建一个具有服务端渲染能力的应用。
2. nuxt.js的优势?
作为框架,Nuxt.js
为 客户端/服务端 这种典型的应用架构模式提供了许多有用的特性,例如异步数据加载、中间件支持、布局支持等。Nuxt.js
有以下比较明显的特性
SASS,LESS
等等HTML
头部标签管理(依赖vue-meta
实现)webpack
配置,无需额外配置3. nuxt.js的使用
npm create nuxt-app <project-name>
4. nuxt.js目录结构
(layouts、pages、static、store、nuxt.config.js、package.json)是Nuxt保留的,不可以更改
5. nuxt.js渲染流程
layout
组件)前执行某种操作,也可以在解析完 layout
之后,解析 page
组件前 执行某种操作。可以理解为是路由器的拦截器的作用Nuxt.js
将自动加载显示 404
错误页面或 500
错误页面,或者进行重定向。vuex
的store
render
:最后进行渲染。将渲染后的页面返回给浏览器,用户在页面进行操作,如果再次请求新的页面,此时只会回到生命周期中的 middlerware 中,而非 nuxtServerInit。nuxt-link
,如果是发起一个新的路由,那么这个时候要从头开始循环我们把服务器端创建的 .vue
文件全部理解成组件,在服务器端环境(node)
通过 beforeCreate
和 created
这俩个生命周期节点后服务器端 vue
组件生命周期结束。返回页面给浏览器,在客户端环境(v8)
中这个 vue
组件实例创建后会在客户端再次拥有生命周期,此时生命周期中有 mounted
等钩子函数。
需要特别注意的是 nuxt
中没有 mounted
钩子函数也没有组件实例,只有 beforeCreate/created
钩子与 context
对象。beforeCreated()
和created()
这两个生命周期函数是同时运行在服务端&&客户端,vue的其他钩子则运行在客户端,所以beforeCreated()
和created()
不存在window
对象
三、nuxt.js渲染过程部分详解
1、nuxtServerInit
举例:打开网页要立即显示的内容
// SSR方式: // 1、nuxtServerInit 方法 actions: { async nuxtServerInit({commit},{req,app}) { let {data: {province, city}} = await axios.get('/aa/bb') commit('home/setPosition',{province: '', city: ''}) } } // 2、middleware 属性 middleware: async (ctx) => { let {data: {province, city}} = await axios.get('/aa/bb') } // NO-SSR vue 组件 mounted 函数发送请求
2、异步数据 asyncData
asyncData
方法会在组件(限于页面组件)每次加载渲染之前,即在服务端或路由更新之前被调用。在asyncData()
中可以处理请求得来的数据,通过return
将处理后的数据返回给当前vue
组件的data
。再次强调这里不能使用this
,因为没有组件实例,asyncData()
默认的参数是ctx
即content
对象。
该方法用来获取数据,在服务器端把异步获取到的数据扔给浏览器,那是如何抛给浏览器的呢?
通过下发一个`script`标签,然后在`window`上挂了一个对象这个对象,第一个是告诉你用的是哪个模板,第二个给你的是数据
3、布局
Nuxt.js布局方式如下图所示:
nuxt.js
实现了一个新的概念,layout
布局,我们可以通过layout
布 局方便的实现页面的多个布局之间方便的切换。具体开发的页面中,如果使用默认布局,则不需指定页面的布局,nuxt
框架会自动对没有指定布局的页面和default
布局进行关联。如果需要指定布局,则在layout
字段中对布局进行指定。
<script> export default { layout: 'plusBuy', ... } </script> // 如果layout文件中建立了一个单独的文件,则在使用中也要指定 <script> export default { layout: 'plusBuy/plusBuy', ... } </script>
四、nuxt爬坑
1、localhost
访问可以,换成真实的ip
地址后访问不了
解决方案:
package.json
里做如下配置"config": { "nuxt": { "host": "0.0.0.0", "port": 3000 } }
2、接口跨域问题
解决方案
@nuxtjs/axios
、@nuxtjs/proxy
nuxt.config.js
做如下配置modules: ['@nuxtjs/axios'], // 不需要加入@nuxtjs/proxy axios: { proxy: true }, proxy: { '/wlfrontend': { // 请求到 /wlfrontend 代理到请求 http://10.102.140.38:7001/wlfrontend target: 'http://10.102.140.38:7001', changeOrigin: true // 如果接口跨域,需要进行这个参数配置 }, '/scenery': { // 将'localhost:8080/scenery/xxx'代理到'https://m.ly.com/scenery/xxx' target: 'https://m.ly.com', // 代理地址 changeOrigin: true, // 如果接口跨域,需要进行这个参数配置 secure: false // 默认情况下,不接受运行在 HTTPS 上,且使用了无效证书的后端服务器。如果你想要接受,只要设置 secure: false } }
3、asyncDate fetch created
因为服务端客户端都会走,如果不想在客户端执行?
async asyncData ({ query, store, req }) { if (!process.server) return } async fetch({ store, params }){ if (!process.server) return } created(){ if (!process.server) return },
4、页面做缓存,也就是返回上一级保持数据不重新请求
解决方案:
在布局页面处理,layout/default.vue
或者是自己建立的布局页面
<template> <div class="plusBuy"> <nuxt keep-alive /> </div> </template>
5、nuxt
是把所有页面的js都引入到主页了?
在生产模式下,Nuxt.js
使用浏览器的预加载策略来预加载目标页面的脚本资源。所以当用户点击某个链接时,会有一种秒开的感觉。预加载策略使得Nuxt.js
既可以保持代码分离又能保证页面访问体验。
<nuxt-link>
则是帮我们扩展了自动预获取代码分割页面。可以使用no-prefetch
属性 禁用
如果想要禁用,在nuxt.config.js
做如下配置
router: { prefetchLinks: false, // 全局禁用所有链接上的预取 } render: { resourceHints: false, // 添加prefetch和preload,以加快初始化页面加载时间。如果有许多页面和路由,可禁用此项 },
6、切换子路由的head
中外部引入脚本载入有延迟,所以在调用时报错
注意:
1、引入脚本不要加async:true
,这样的话脚本不会阻塞,在下面代码有用到该脚本中的方式时,脚本可能还没有加载完
2、需要每个小项目自己做个定制化页面layout
,layout/我的目录/我的页面.vue 然后在定制化页面中使用head()
加入脚本
export default { // 方式一: head: { script: [ { type: 'text/javascript', src: 'https://js.40017.cn/cn/min/??/touch/hb/c/bridge.2.1.4.js?v=2016053', defer: true } ] } // 方式二: head () { return { script: [ { type: 'text/javascript', src: 'https://js.40017.cn/cn/min/??/touch/hb/c/bridge.2.1.4.js?v=2016053', defer: true } ] } } }
7、滚动事件
如果html
和body
设置了100%,那么子页面足够长时滚动的话,滚动事件要绑定在子页面上,因为body
的高度不是整个页面的高度
// 1. 在子页面父元素加 <template> <div class="plus" ref="mainPage"></div> </template> // 2. 样式设置100%滚动 .plus { height: 100%; overflow-y: scroll; -webkit-overflow-scrolling : touch; } // 3. 再添加滚动事件 function scrollEvent() { var that = this; let dom = this.$refs.mainPage; dom.onscroll = function() { let wh = dom.scrollTop; // 页面上滑,出现 wh > 100 ? (that.showBackTop = true) : (that.showBackTop = false); // 未开通,页面滑动至不出现顶部的立即开通按钮时,底部的立即开通固定展示 if(that.memberRightsInfo && !that.memberRightsInfo.IsPlusMember){ if(document.querySelector('.tab') && document.querySelector('.tab').offsetTop){ let distance = document.querySelector('.tab').offsetTop; wh > distance - 50 ? (that.isShowFixedBtn = true) : (that.isShowFixedBtn = false); } } }; }
8、文件下建立了其他文件,比如store/plusBuy/index.js
,并没有在store
下直接建立index.js
,如何使用?
原理:Nuxt把store中的index.js文件中所有的state、mutations、actions、getters都作为其公共属性挂载到了store实例上,然而其他的文件则是使用的是命名空间,其对应的命名空间的名字就是其文件名。
computed: { ...mapState('plusBuy', { nickName: state => state.nickName }) } ...mapMutations('plusBuy', { setCityId: 'setCityId' // 将 `this.setCityId()` 映射为 `this.$store.commit('setCityId')` }) ...mapActions('plusBuy', { login: 'login' // 将 `this.login()` 映射为 `this.$store.dispatch('login')` })
9、asyncData
不可以调用this
,如果有好多个异步或数据进行处理,如何优化asyncData()
// 可以使用类 class A { aatest(aa){ console.log(aa) } } // 调用方法 async asyncData ({ query, store, req }) { var test = new A(); test.aatest(123); }
10、如何获取cookie
// 服务端获取cookie b_getToken(req = {},c_name){ if (req.headers && req.headers.cookie) { var req_Cookies = req.headers.cookie.split("; ") let tokens = '' req_Cookies.forEach(v => { if (v.indexOf(c_name + "=")>=0) { tokens = v } }) return tokens.split('=')[1] } else { return '' } } // 客户端获取cookie getCookie: function(c_name) { if (document.cookie.length > 0) { //先查询cookie是否为空,为空就return "" let c_start = document.cookie.indexOf(c_name + "=") || ''; //通过String对象的indexOf()来检查这个cookie是否存在,不存在就为 -1 if (c_start != -1) { c_start = c_start + c_name.length + 1; //最后这个+1其实就是表示"="号啦,这样就获取到了cookie值的开始位置 let c_end = document.cookie.indexOf(";", c_start); // 为了得到值的结束位置。因为需要考虑是否是最后一项,所以通过";"号是否存在来判断 if (c_end == -1) { c_end = document.cookie.length; } return unescape(document.cookie.substring(c_start, c_end)); } } return ""; }, // 调用 let token = ''; if(process.server){ token = serverUtilsFn.b_getToken(req,'17uCNRefId'); console.log('server:' + token) }else { token = utilsFn.getCookie('17uCNRefId'); console.log('client:' + token) }
11、axios
数据处理问题,重复问题
import axios from 'axios'; import requestCheck from './requestCheck'; // 确保使用 axios.create创建实例后再使用。否则多次刷新页面请求服务器,服务端渲染会重复添加拦截器,导致数据处理错误 const myaxios = axios.create() // axios.defaults.baseURL = "http://localhost:3000/" myaxios.interceptors.request.use(config => { let req = {...config }; req.url = req.method.toLocaleLowerCase() == 'post' ? requestCheck(req.url, req.data) : requestCheck(req.url, req.params); return req; }, error => { return Promise.reject(error) }) myaxios.interceptors.response.use(response => { return response }, error => { return Promise.reject(error) }) export default myaxios;
12、跳转路由传递参数并且取值
传递参数 -- this.$router.push({name: ' 路由的name ', params: {key: value}})
参数取值 -- this.$route.params.key
注: 使用这种方式,参数不会拼接在路由后面,地址栏上看不到参数
注意: 由于动态路由也是传递params的,所以在 this.$router.push() 方法中 path不能和params一起使用,否则params将无效。需要用name来指定页面。
13、设置页面动画效果
/* 全局过渡动效设置 - 淡出 (fade) 效果*/ .page-enter-active, .page-leave-active { transition: opacity .5s; } .page-enter, .page-leave-active { opacity: 0; } /* 局部过渡动效设置 - 淡出 (fade) 效果*/ .test-enter-active, .test-leave-active { transition: opacity .5s; } .test-enter, .test-leave-active { opacity: 0; } // 在要使用的组件页面中 export default { transition: 'test', }
14、如何使用插件
// 1. 安装插件 yarn add swiper -D // 2. 引入 <script> import Swiper from 'swiper' </script> // 3. 引入样式 <style lang="less" scoped> @import "../../node_modules/swiper/css/swiper.css"; </style>
15、如何在组件中使用异步数据
如果组件不是和路由绑定的页面组件,原则上是不可以使用异步数据的。因为 Nuxt.js 仅仅扩展增强了页面组件的data
方法,使得其可以支持异步数据处理。
对于非页面组件,有两种方式可以实现数据的异步获取:
mounted
方法里面实现异步获取数据的逻辑,之后设置组件的数据,限制是:不支持服务端渲染。asyncData
或fetch
方法中进行API调用,并将数据作为props
传递给子组件。服务器渲染工作正常。缺点:asyncData
或页面提取可能不太可读,因为它正在加载其他组件的数据。总之,使用哪种方法取决于你的应用是否需要支持子组件的服务端渲染。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持亿速云。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。