详解Nuxt.js 实战集锦
读本文前,请先熟读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原理图
SSR是处于CSR与SPA应用之间的一个折中的方案,在渲染首屏的时候在服务端做出了渲染,注意仅仅是首屏,其他页面还是需要在客户端渲染的,在服务端接收到请求之后并且渲染出首屏页面,会携带着剩余的路由信息预留给客户端去渲染其他路由的页面。
vueSSR
将本来要放在浏览器执行创建的组件,放到服务端先创建好,然后生成对应的html将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。
在浏览器第一次访问某个URI资源的时候(首屏),Web服务器根据路由拿到对应数据渲染并输出,且输出的数据中包含两部分:
- 路由页对应的页面及已渲染好的数据
- 完整的SPA程序代码
在首屏渲染完成之后,此时我们看到的其实已经是一个和之前的SPA相差无几的应用程序了,接下来我们进行的任何操作都只是客户端的应用进行交互,页面/组件由Web端渲染,路由也由浏览器控制,用户只需要和当前浏览器内的应用打交道就可以了。
vueSSR原理图
(layouts、pages、static、store、nuxt.config.js、package.json)是Nuxt保留的,不可以更改
5. nuxt.js渲染流程
我们把服务器端创建的 .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`上挂了一个对象这个对象,第一个是告诉你用的是哪个模板,第二个给你的是数据
在布局页面处理,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或页面提取可能不太可读,因为它正在加载其他组件的数据。
总之,使用哪种方法取决于你的应用是否需要支持子组件的服务端渲染。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持来客网。