当前位置:   article > 正文

Vue 0基础学习路线(27)—— 图解深度详述vue路由的懒加载的使用及原理和深入原理原生js探究静态加载和懒加载、合并打包 - 把组件按组分块及详细案例(附详细案例代码解析过程及版本迭代过程)_vue路由懒加载 流程图

vue路由懒加载 流程图

1. 引言

1.1 项目打包

我们打包一下上一篇的项目

yarn build

在这里插入图片描述

生成dist目录

在这里插入图片描述

跑起来

npx http-server ./dist -c-l

在这里插入图片描述

这块代码小迪就不上传了,大家自己clone下来上一篇最新的代码,自己打包吧!

1.2 加载问题深入探究

大体没啥问题,我们看一下加载的问题

我们看一下打包文件下的js,有一个chunk开头的文件,其实它是一些公共库(如:vuevue-router等),app其实是打包我们所写的js文件了。

我们刚刚写的三个页面HomeAboutDetail页面最终会转成js组件,但是会打包成一个js文件。即所有代码均在js下的app文件中。

我们可以想象一下,如果页面很多的话,把所有的页面都打包到一起,致使这个js文件非常庞大。非常不利用我们首屏加载速度,第一次打开的时候会非常卡顿,这个时候就需要我们本篇所学的东西了,路由懒加载

在这里插入图片描述

它其实就是按需加载,我们只有访问了这个路由(组件)以后才会加载,而不是一上来全部加载完,用import解决,它是我们浏览器中新增的一个东西,称为动态加载,也即懒加载

它其实还有一个对应的东西,叫做静态加载

2. 深入原理原生js探究静态加载

2.1 example01

2.1.1 example01-1

平时开发时候,我们在文件头部或script标签下的头部用import导入其他库或者文件

lazyImport\js\a.js

import b from './b.js';
 
console.log(b);
  • 1
  • 2
  • 3

lazyImport\js\b.js

export default 100;
  • 1

lazyImport\1.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 
<script type="module" src="./js/a.js"></script>
 
</body>
</html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在这里插入图片描述

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a2.35
Branch: branch07

commit description:a2.35(example01-1——原生js常规静态导入)

tag:a2.35

2.1.2 example01-2

以上直接引用没有任何问题,我们试试点击之后再加载b

\lazyImport\js\a.js

import b from './b.js';
 
// console.log(b);
 
document.onclick = async function() {
    console.log(b);
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

实际上这种语法静态加载 => 不能根据需求动态地去加载,而是import先写好。

在这里插入图片描述

我们其实很容易发现,页面刷新的时候,其实就把a、b文件都加载了,如果项目庞大,我们加载上万个文件,这首屏显示慢到炸了,用户最多等几秒可能就不想再看你写的网页了。

在这里插入图片描述

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a2.36
Branch: branch07

commit description:a2.36(example01-2——原生js常规静态导入-点击之后加载b)

tag:a2.36

2.1.3 example01-3

我们正常开发时为了更好的用户体验,通常需要懒加载的 ,即按需加载。

document.onclick = async function() {
    import b from './b.js';
    console.log(b)
};
  • 1
  • 2
  • 3
  • 4

静态加载实现不了懒加载,点击以后报错了。

在这里插入图片描述

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a2.37
Branch: branch07

commit description:a2.37(example01-3——静态加载无法按需引入)

tag:a2.37

3. 深入原理原生js实现懒加载

实际上静态加载不太好,管你用不用,直接全部加载进来,试想象一下,如果引入上百万多个文件,这首屏渲染不卡爆了,估计用户在看这个网页的时候,都想把显示器砸了。

那我们利用原生js实现一下懒加载。

3.1 example02

3.1.1 example02-1

懒加载就没必要再用type属性了,因为它并不通过静态加载的import语法了,而是通过import函数实现加载

lazyImport\2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 
<script src="./js/a.js"></script>
 
</body>
</html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

\lazyImport\js\a.js

document.onclick = function() {
    import('./b.js')
};
  • 1
  • 2
  • 3

一上来,network没有b.js,点击以后才会b.js,并且没有报错。

在这里插入图片描述

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a2.38
Branch: branch07

commit description:a2.38(example02-1——原生js实现懒加载)

tag:a2.38

3.1.2 example02-2

并且import函数的返回值一个promise,我们不用then方法了,可以把函数变为async => 打印一下返回的对象

document.onclick = async function() {
    // console.log(b);
 
    let result = await import('./b.js');
    console.log(result)
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

得到的是一个Moudle对象,它底下有一个default属性,它就是导入的默认值。

在这里插入图片描述

如果不是通过默认值导出,而是导出一个对象,则可以通过obj属性获取了。

\lazyImport\js\b.js

export default 100;
 
export let obj = {
    x: 1,
    y: 2
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在这里插入图片描述

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a2.39
Branch: branch07

commit description:a2.39(example02-2——原生js实现懒加载-获取导出对象和默认值)

tag:a2.39

以上就是原生js的模块懒加载方式了。

4. 路由懒加载

当打包构建应用时,JavaScript包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了

import Vue from 'vue'
import Router from 'vue-router'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'

// import Home from './views/Home.vue'
// import About from './views/About.vue'
// import Item from './views/Item.vue'
// import User from './views/User.vue'
// import Profile from './views/User/Profile'
// import Cart from './views/User/Cart'
// import BookChoose from './views/Book/BookChoose'
// import BookBoy from './views/Book/BookBoy'
// import BookGirl from './views/Book/BookGirl'

Vue.use(Router)

const router = new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  scrollBehavior (to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition
    } else {
      return { x: 0, y: 0 }
    }
  },
  routes: [
    {
      path: '/',
      name: 'home',
      component: () => import('./views/Home.vue')
    },
    {
      path: '/about',
      name: 'about',
      component: () => import('./views/About.vue')
    },
    {
      path: '/item/:itemId',
      name: 'item',
      component: () => import('./views/Item.vue'),
      props: r => ({ itemId: Number(r.params.itemId) })
    },
    {
      path: '/user',
      component: () => import(/* webpackChunkName: "user" */ './views/User.vue'),
      meta: { requiresAuth: true },
      children: [
        {
          path: '',
          name: 'user',
          component: () => import(/* webpackChunkName: "user" */ './views/User/Profile.vue')
        },
        {
          path: 'cart',
          name: 'user-cart',
          component: () => import(/* webpackChunkName: "user" */ './views/User/Cart.vue')
        }
      ]
    },
    {
      path: '/book',
      name: 'book',
      // redirect: { name: 'book-choose' }
      redirect: to => {
        let type = localStorage.getItem('book-type')
        return { name: type || 'book-choose' }
      }
    },
    {
      path: '/book-choose',
      name: 'book-choose',
      component: () => import(/* webpackChunkName: "book" */ './views/Book/BookChoose.vue')
    },
    {
      path: '/book-boy',
      name: 'book-boy',
      component: () => import(/* webpackChunkName: "book" */ './views/Book/BookBoy.vue')
    },
    {
      path: '/book-girl',
      name: 'book-girl',
      component: () => import(/* webpackChunkName: "book" */ './views/Book/BookGirl.vue')
    }
  ]
})

router.beforeEach((to, from, next) => {
  NProgress.start()

  if (to.matched.some(record => record.meta.requiresAuth) && !isLogin) {
      next({
        name: 'login',
      })
  } else {
    next()
  }

  next()
})

router.afterEach((to, from, next) => {
  NProgress.done()
})

export default router

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108

4.1 官方解释

路由懒加载 =>

当打包构建应用时,JavaScript包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。

结合Vue异步组件Webpack代码分割功能,轻松实现路由组件的懒加载。

首先,可以将异步组件定义为返回一个Promise的工厂函数 (该函数返回的Promise应该resolve组件本身):

const Foo = () => Promise.resolve({ /* 组件定义对象 */ })
  • 1

第二,在Webpack 2中,我们可以使用动态 import语法来定义代码分块点 (split point):

import('./Foo.vue') // 返回 Promise
  • 1

注意

如果您使用的是 Babel,你将需要添加 syntax-dynamic-import 插件,才能使 Babel 可以正确地解析语法。

结合这两者,这就是如何定义一个能够被Webpack 自动代码分割的异步组件。

const Foo = () => import('./Foo.vue')
  • 1

在路由配置中什么都不需要改变,只需要像往常一样使用 Foo

const router = new VueRouter({
  routes: [
    { path: '/foo', component: Foo }
  ]
})
  • 1
  • 2
  • 3
  • 4
  • 5

5. 路由懒加载实例

回过头,我们看vue =>

注意 import是一个函数,不是对象,把这个函数赋给当前的组件,这个函数来加载组件。

它现在是加载的函数给了当前的组件,而不是静态加载,一下就加载了。

而是当我们访问路由的时候,这个加载函数才会执行。 => 动态记载路由

const Home = function() {
    return import('@/views/Home');
};
  • 1
  • 2
  • 3

可以箭头函数简写:

const Home = () => import('@/views/Home');
const About = () => import('@/views/About');
const Detail = () => import('@/views/Detail');
  • 1
  • 2
  • 3

参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a2.40
Branch: branch07

commit description:a2.40(路由懒加载)

tag:a2.40

再打包

yarn build

在这里插入图片描述

我们发现打包后生成的文件也不一样了,一个import引入就为我们生成一个js文件了。

在这里插入图片描述

再去访问

npx http-server ./dist -c-l

在这里插入图片描述

但是感觉一上来还是全部加载了,其实本质上它不会影响我们懒加载的,

在这里插入图片描述

5.1 深入探究

我们看一下打包的index.html

在这里插入图片描述

能发现它是通过link标签去加载的,而且有一个rel属性,值为preload =>

预加载,而不是真实加载,它不会阻塞和影响当前我们的整个页面的后续渲染。

我们首屏渲染的目的是,如果你用script标签的话,多少肯定会影响首屏渲染的性能,这种方式就可以减少首屏渲染加载的时间了,它是一种预处理。

我们点击对应组件它才会真正去加载

在这里插入图片描述

我们最开始加载的时候,我们看到他大小都是0kb,只有点击之后,才能正在加载,才有了大小。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

除了加快首屏渲染,它还有一个特性即缓存特性,我们下一次调用的时候直接去缓存取了,不会重复加载,所以速度会提高很多。

6. 合并打包 - 把组件按组分块

有时候我们想把某个路由下的所有组件都打包在同个异步块 (chunk) 中。只需要使用 命名 chunk,一个特殊的注释语法来提供 chunk name (需要 Webpack > 2.4)。

const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')
  • 1
  • 2
  • 3

Webpack会将任何一个异步模块与相同的块名称组合到相同的异步块中。

import 里加一个注释 =>

默认情况下是一个import函数打一个包,但有的时候不希望这么做。

如现在user底下有很多组件,但是呢?

user底下想打成一个js包,不要分开那么多,我们可以让其在打包过程中加如下注释,把如下三个组件webpackChunkName起一样的名称即可,这样就把三个组件相关的代码打包到userjs里了,即合并到一块。

component: () => import(/* webpackChunkName: "user" */ './views/User/Profile.vue')
  • 1

7. 功能性组件懒加载

当然除了以上加载外,还可以在组件当中使用它。

比如在Home当中有一个page,在page中也可以进行懒加载

  components: {
            Page: () => import('@/components/Page')
        },
  • 1
  • 2
  • 3

考虑到在blog中不好体现代码更改的位置,小迪才用github托管代码,大家可以查看github,看到详细版本修改过程,搭配博客学习。



(后续待补充)
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/363669
推荐阅读
相关标签
  

闽ICP备14008679号