详解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方法,使得其可以支持异步数据处理。

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

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

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持来客网。