详解Nuxt.js 实战集锦


Posted in Javascript onNovember 19, 2019

读本文前,请先熟读nuxt官方文档,并且具备一定的vue.js相关开发经验

中文文档
英文文档
vue SSR指南

一、CSR和SSR对比

SPA之前的时代,我们传统的Web架构大都是SSR,如:Wordpress(PHP)JSP技术、JavaWeb等这些程序都是传统典型的SSR架构,即:服务端取出数据和模板组合生成 html输出给前端,前端发生请求时,重新向服务端请求html资源。

SPA(CSR):

SPA应用,到了VueReact,单页面应用优秀的用户体验,逐渐成为了主流,页面整体是javaScript渲染出来的,称之为客户端渲染CSRSPA渲染过程。由客户端访问URL发送请求到服务端,返回HTML结构(但是SPA的返回的HTML结构是非常的小的,只有一个基本的结构)。客户端接收到返回结果之后,在客户端开始渲染HTML,渲染时执行对应javaScript,最后渲染template,渲染完成之后,再次向服务端发送数据请求,注意这里时数据请求,服务端返回json格式数据。客户端接收数据,然后完成最终渲染。

CSR原理图

详解Nuxt.js 实战集锦

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原理图

详解Nuxt.js 实战集锦

SSR是处于CSRSPA应用之间的一个折中的方案,在渲染首屏的时候在服务端做出了渲染,注意仅仅是首屏,其他页面还是需要在客户端渲染的,在服务端接收到请求之后并且渲染出首屏页面,会携带着剩余的路由信息预留给客户端去渲染其他路由的页面。

vueSSR

将本来要放在浏览器执行创建的组件,放到服务端先创建好,然后生成对应的html将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。

在浏览器第一次访问某个URI资源的时候(首屏),Web服务器根据路由拿到对应数据渲染并输出,且输出的数据中包含两部分:

  • 路由页对应的页面及已渲染好的数据
  • 完整的SPA程序代码

在首屏渲染完成之后,此时我们看到的其实已经是一个和之前的SPA相差无几的应用程序了,接下来我们进行的任何操作都只是客户端的应用进行交互,页面/组件由Web端渲染,路由也由浏览器控制,用户只需要和当前浏览器内的应用打交道就可以了。

vueSSR原理图

详解Nuxt.js 实战集锦

webpackSource 打包出两个bundle文件:其中 Server Bundle用于服务端渲染,服务端通过渲染器 bundleRendererbundle 生成首屏html片段;而 Client Bundle 用于客户端渲染,首屏外的交互和数据处理还是需要浏览器执行 Client Bundle 来完成

缺点:

  • 开发条件所限。浏览器特定的代码,只能在某些生命周期钩子函数(lifecycle hook)中使用;一些外部扩展库(external library)可能需要特殊处理,才能在服务器渲染应用程序中运行。
  • 更多的服务器端负载。在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用 CPU 资源,因此如果你预料在高流量环境(high traffic)下使用,请准备相应的服务器负载,并明智地采用缓存策略。

二、nuxt.js介绍

1. nuxt.js是什么?

Nuxt.jsvue官方推荐的一个基于 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目录结构

详解Nuxt.js 实战集锦

(layouts、pages、static、store、nuxt.config.js、package.json)是Nuxt保留的,不可以更改

5. nuxt.js渲染流程

详解Nuxt.js 实战集锦

  • Incoming Request指的是浏览器发出一个请求,服务端接收请求后
  • 要检查当前有没有nuxtServerInit这个配置项,如果有的话就先执行这个函数。具体的作用和使用可参考官方文档nuxtserverinit-方法
  • middleware中间件,中间件允许您定义一个自定义函数运行在一个页面或一组页面渲染之前。也就是可以在 匹配布局(layout 组件)前执行某种操作,也可以在解析完 layout 之后,解析 page 组件前 执行某种操作。可以理解为是路由器的拦截器的作用
  • 验证:validate(),可以配合高级动态路由去做验证。如果校验不通过,Nuxt.js 将自动加载显示 404 错误页面或 500 错误页面,或者进行重定向。
  • 获取数据,asyncData方法获取数据并返回给当前组件,fetch方法修改vuexstore
  • render:最后进行渲染。将渲染后的页面返回给浏览器,用户在页面进行操作,如果再次请求新的页面,此时只会回到生命周期中的 middlerware 中,而非 nuxtServerInit。
  • nuxt-link,如果是发起一个新的路由,那么这个时候要从头开始循环

详解Nuxt.js 实战集锦

我们把服务器端创建的 .vue 文件全部理解成组件,在服务器端环境(node)通过 beforeCreatecreated 这俩个生命周期节点后服务器端 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() 默认的参数是 ctxcontent 对象。

该方法用来获取数据,在服务器端把异步获取到的数据扔给浏览器,那是如何抛给浏览器的呢?

通过下发一个`script`标签,然后在`window`上挂了一个对象这个对象,第一个是告诉你用的是哪个模板,第二个给你的是数据

详解Nuxt.js 实战集锦

3、布局

Nuxt.js布局方式如下图所示:

详解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地址后访问不了

解决方案:

  1. 确认有没有开代理
  2. 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、滚动事件

如果htmlbody设置了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方法,使得其可以支持异步数据处理。

对于非页面组件,有两种方式可以实现数据的异步获取:

  1. 在组件的mounted方法里面实现异步获取数据的逻辑,之后设置组件的数据,限制是:不支持服务端渲染。
  2. 在页面组件的asyncDatafetch方法中进行API调用,并将数据作为props传递给子组件。服务器渲染工作正常。缺点:asyncData或页面提取可能不太可读,因为它正在加载其他组件的数据。

总之,使用哪种方法取决于你的应用是否需要支持子组件的服务端渲染。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JS下高效拼装字符串的几种方法比较与测试代码
Apr 15 Javascript
javascript读写json示例
Apr 11 Javascript
JavaScript中的Math 使用介绍
Apr 21 Javascript
node.js中的fs.open方法使用说明
Dec 17 Javascript
IE下使用jQuery重置iframe地址时内存泄露问题解决办法
Feb 05 Javascript
Javascript编写2048小游戏
Jul 07 Javascript
js点击文本框弹出可选择的checkbox复选框
Feb 03 Javascript
js实现控制textarea输入字符串的个数,鼠标按下抬起判断输入字符数
Oct 25 Javascript
使用jQuery ajaxupload插件实现无刷新上传文件
Apr 23 jQuery
Angular2整合其他插件的方法
Jan 20 Javascript
es6 filter() 数组过滤方法总结
Apr 03 Javascript
pm2发布node配置文件ecosystem.json详解
May 15 Javascript
javascript的delete运算符知识点总结
Nov 19 #Javascript
100行代码实现vue表单校验功能(小白自编)
Nov 19 #Javascript
Angular 多级路由实现登录页面跳转(小白教程)
Nov 19 #Javascript
nodemon实现Typescript项目热更新的示例代码
Nov 19 #Javascript
vue的三种图片引入方式代码实例
Nov 19 #Javascript
JS在Array数组中按指定位置删除或添加元素对象方法示例
Nov 19 #Javascript
JavaScript(js)处理的HTML事件、键盘事件、鼠标事件简单示例
Nov 19 #Javascript
You might like
mysq GBKl乱码
2006/11/28 PHP
php $_SERVER当前完整url的写法
2009/11/12 PHP
php 一元分词算法
2009/11/30 PHP
PHP对象Object的概念 介绍
2012/06/14 PHP
javascript vvorld 在线加密破解方法
2008/11/13 Javascript
子窗口、父窗口和Silverlight之间的相互调用
2010/08/16 Javascript
javascript定时保存表单数据的代码
2011/03/17 Javascript
jQuery调用WebService的实现代码
2011/06/19 Javascript
JQuery实现点击div以外的位置隐藏该div窗口
2013/09/13 Javascript
微信JS接口汇总及使用详解
2015/01/09 Javascript
详解JavaScript中循环控制语句的用法
2015/06/03 Javascript
JS实现从顶部下拉显示的带动画QQ客服特效代码
2015/10/24 Javascript
Hallo.js基于jQuery UI所见即所得的Web编辑器
2016/01/26 Javascript
JQ选择器_选择同类元素的第N个子元素的实现方法
2016/09/08 Javascript
网页瀑布流布局jQuery实现代码
2016/10/21 Javascript
深入理解jQuery()方法的构建原理
2016/12/05 Javascript
JS定时器实现数值从0到10来回变化
2016/12/09 Javascript
微信小程序 数组(增,删,改,查)等操作实例详解
2017/01/05 Javascript
VueJS如何引入css或者less文件的一些坑
2017/04/25 Javascript
详解vue 自定义组件使用v-model 及探究其中原理
2019/10/11 Javascript
Vue 中 template 有且只能一个 root的原因解析(源码分析)
2020/04/11 Javascript
Python开发常用的一些开源Package分享
2015/02/14 Python
python装饰器实例大详解
2017/10/25 Python
PyCharm代码回滚,恢复历史版本的解决方法
2018/10/22 Python
pygame实现雷电游戏雏形开发
2018/11/20 Python
python实现三维拟合的方法
2018/12/29 Python
详解python selenium 爬取网易云音乐歌单名
2019/03/28 Python
python中导入 train_test_split提示错误的解决
2020/06/19 Python
师范生自我鉴定范文
2013/10/05 职场文书
顶岗实习计划书
2014/01/10 职场文书
五月的鲜花活动方案
2014/08/21 职场文书
医院深入开展党的群众路线教育实践活动实施方案
2014/08/27 职场文书
2014年庆祝国庆65周年演讲稿
2014/09/21 职场文书
2015年世界水日活动总结
2015/02/09 职场文书
宝葫芦的秘密观后感
2015/06/11 职场文书
红色影片观后感
2015/06/18 职场文书