JavaScript
主页 > 网络编程 > JavaScript >

Vue.js前端路由和异步组件的介绍

2022-09-13 | 佚名 | 点击:

文章目标

P6

P6+ ~ P7

一、背景

远古时期,当时前后端还是不分离的,路由全部都是由服务端控制的,前端代码和服务端代码过度融合在一起。

客户端 --> 前端发起 http 请求 --> 服务端 --> url 路径去匹配不同的路由 --> 返回不同的数据。

这种方式的缺点和优点都非常明显:

后来 ...随之 ajax 的流行,异步数据请求可以在浏览器不刷新的情况下进行。

后来 ...出现了更高级的体验 —— 单页应用。

在单页应用中,不仅在页面中的交互是不刷新页面的,就连页面跳转也都是不刷新页面的。

单页应用的特点:

而支持起单页应用这种特性的,就是 前端路由。

二、前端路由特性

前端路由的需求是什么?

也就是可以在改变 url 的前提下,保证页面不刷新。

三、面试!!!

Hash 路由和 History 路由的区别?

四、Hash 原理及实现

1、特性

hash 的出现满足了这个需求,他有以下几种特征:

1

2

3

location.hash = '#aaa';

location.hash = '#bbb';

// 从 #aaa 到 #bbb,页面是不会刷新的

1

2

3

4

location.hash = '#aaa';

location.hash = '#bbb';

 

window.addEventLisenter('hashchange', () => {});

2、如何更改 hash

我们同样有两种方式来控制 hash 的变化:

1

2

location.hash = '#aaa';

location.hash = '#bbb';

1

2

3

4

<a href="#user" rel="external nofollow" > 点击跳转到 user </a>

 

<!-- 等同于下面的写法 -->

location.hash = '#user';

3、手动实现一个基于 hash 的路由

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

<!DOCTYPE html>

<html lang="en">

  <head>

    <meta charset="UTF-8" />

    <meta http-equiv="X-UA-Compatible" content="IE=edge" />

    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <title>Document</title>

    <link rel="stylesheet" href="./index.css" rel="external nofollow"  />

  </head>

  <body>

    <div class="container">

      <a href="#gray" rel="external nofollow" >灰色</a>

      <a href="#green" rel="external nofollow" >绿色</a>

      <a href="#" rel="external nofollow" >白色</a>

      <button onclick="window.history.go(-1)">返回</button>

    </div>

 

    <script type="text/javascript" src="index.js"></script>

  </body>

</html>

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

.container {

  width: 100%;

  height: 60px;

  display: flex;

  justify-content: space-around;

  align-items: center;

 

  font-size: 18px;

  font-weight: bold;

 

  background: black;

  color: white;

}

 

a:link,

a:hover,

a:active,

a:visited {

  text-decoration: none;

  color: white;

}

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

/*

  期望看到的效果:点击三个不同的 a 标签,页面的背景颜色会随之变化

*/

class BaseRouter {

  constructor() {

    this.routes = {}; // 存储 path 以及 callback 的对应关系

    this.refresh = this.refresh.bind(this); // 如果不 bind 的话,refresh 方法中的 this 指向 window

    // 处理页面 hash 变化,可能存在问题:页面首次进来可能是 index.html,并不会触发 hashchange 方法

    window.addEventListener('hashchange', this.refresh);

 

    // 处理页面首次加载

    window.addEventListener('load', this.refresh);

  }

 

  /**

   * route

   * @param {*} path 路由路径

   * @param {*} callback 回调函数

   */

  route(path, callback) {

    console.log('========= route 方法 ========== ', path);

    // 向 this.routes 存储 path 以及 callback 的对应关系

    this.routes[path] = callback || function () {};

  }

 

  refresh() {

    // 刷新页面

    const path = `/${location.hash.slice(1) || ''}`;

    console.log('========= refresh 方法 ========== ', path);

    this.routes[path]();

  }

}

 

const body = document.querySelector('body');

function changeBgColor(color) {

  body.style.backgroundColor = color;

}

 

const Router = new BaseRouter();

 

Router.route('/', () => changeBgColor('white'));

Router.route('/green', () => changeBgColor('green'));

Router.route('/gray', () => changeBgColor('gray'));

五、History 原理及实现

hash 有个 # 符号,不美观,服务端无法接受到 hash 路径和参数。

历史的车轮无情撵过 hash,到了 HTML5 时代,推出了 History API。

1、HTML5 History 常用的 API

1

2

3

4

5

6

7

8

9

window.history.back(); // 后退

 

window.history.forward(); // 前进

 

window.history.go(-3); // 接收 number 参数,后退 N 个页面

 

window.history.pushState(null, null, path);

 

window.history.replaceState(null, null, path);

其中最主要的两个 API 是 pushState 和 replaceState,这两个 API 都可以在不刷新页面的情况下,操作浏览器历史记录。

不同的是,pushState 会增加历史记录,replaceState 会直接替换当前历史记录。

2、pushState/replaceState 的参数

他们的参数是?样的,三个参数分别是:

3、History 的特性

History API 有以下几个特性:

4、面试!!!

5、手动实现一个基于 History 的路由

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

.container {

  width: 100%;

  height: 60px;

  display: flex;

  justify-content: space-around;

  align-items: center;

 

  font-size: 18px;

  font-weight: bold;

 

  background: black;

  color: white;

}

 

a:link,

a:hover,

a:active,

a:visited {

  text-decoration: none;

  color: white;

}

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

class BaseRouter {

  constructor() {

    this.routes = {};

 

    // location.href; => hash 的方式

    console.log('location.pathname ======== ', location.pathname); // http://127.0.0.1:8080/green ==> /green

    this.init(location.pathname);

    this._bindPopState();

  }

 

  init(path) {

    // pushState/replaceState 不会触发页面的渲染,需要我们手动触发

    window.history.replaceState({ path }, null, path);

    const cb = this.routes[path];

    if (cb) {

      cb();

    }

  }

 

  route(path, callback) {

    this.routes[path] = callback || function () {};

  }

 

  // ! 跳转并执行对应的 callback

  go(path) {

    // pushState/replaceState 不会触发页面的渲染,需要我们手动触发

    window.history.pushState({ path }, null, path);

    const cb = this.routes[path];

    if (cb) {

      cb();

    }

  }

  // ! 演示一下 popstate 事件触发后,会发生什么

  _bindPopState() {

    window.addEventListener('popstate', e => {

      /*

        触发条件:

          1、点击浏览器前进按钮

          2、点击浏览器后退按钮

          3、js 调用 forward 方法

          4、js 调用 back 方法

          5、js 调用 go 方法

      */

      console.log('popstate 触发了');

      const path = e.state && e.state.path;

      console.log('path >>> ', path);

      this.routes[path] && this.routes[path]();

    });

  }

}

const Router = new BaseRouter();

const body = document.querySelector('body');

const container = document.querySelector('.container');

 

function changeBgColor(color) {

  body.style.backgroundColor = color;

}

Router.route('/', () => changeBgColor('white'));

Router.route('/gray', () => changeBgColor('gray'));

Router.route('/green', () => changeBgColor('green'));

 

container.addEventListener('click', e => {

  if (e.target.tagName === 'A') {

    e.preventDefault();

    console.log(e.target.getAttribute('href')); // /gray  /green 等等

    Router.go(e.target.getAttribute('href'));

  }

});

六、Vue-Router

1、router 使用

使用 Vue.js,我们已经可以通过组合组件来组成应用程序,当你要把 Vue Router 添加进来,我们需要做的是,将组件(components)映射到路由(routes),然后告诉 Vue Router 在哪里渲染它们。

举个例子:

1

2

3

4

<!-- 路由匹配到的组件将渲染在这里 -->

<div id="app">

  <router-view></router-view>

</div>

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

// 如果使用模块化机制编程,导入 Vue 和 VueRouter,要调用 Vue.use(VueRouter)

 

// 1、定义(路由)组件

// 可以从其他文件 import 进来

const Foo = { template: '<div>foo</div>' };

const Bar = { template: '<div>bar</div>' };

 

// 2、定义路由

//每个路由应该映射一个组件,其中 component 可以是通过 Vue.extend() 创建的组件构造器,或者只是一个组件配置对象

const routes = [

  { path: '/foo', component: Foo },

  { path: '/bar', component: Bar },

];

 

// 3、创建 router 实例,然后传 routes 配置

const router = new VueRouter({

  routes,

});

 

// 4、创建和挂载根实例

// 记得要通过 router 配置参数注入路由,从而让整个应用都有路由功能

const app = new Vue({

  router,

}).$mount('#app');

2、动态路由匹配

我们经常需要把某种模式匹配到的所有路由,全部映射到同个组件,比如用户信息组件,不同用户使用同一个组件。

可以通过 $route.params.id 或者参数。

1

2

3

4

5

6

7

8

9

10

const router = new VueRouter({

  routes: [

    // 动态路径参数,以冒号开头

    { path: '/user/:id', component: User },

  ],

});

 

const User = {

  template: '<div>User: {{ $route.params.id }}</div>',

};

3、响应路由参数的变化

复用组件时,想对 路由参数 的变化作出响应的话,可以使用 watch 或者 beforeRouteUpdate:

举个例子:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

const User = {

  template: '...',

  watch: {

    $route(to, from) {

      // 对路由变化作出响应...

    },

  },

};

 

const User = {

  template: '...',

  beforeRouteUpdate(to, from, next) {

    // 对路由变化作出响应...

    // don't forget to call next()

  },

};

4、捕获所有路由或 404 Not found 路由

当时用通配符路由时,请确保路由的顺序是正确的,也就是说含有通配符的路由应该在 最后。

举个例子:

5、导航守卫

vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种方式植入路由导航过程中:

6、完整的导航解析流程

举个例子:

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

// 全局

const router = new VueRouter({

  mode: 'history',

  base: process.env.BASE_URL,

  routes,

});

 

// 全局的导航守卫

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

  console.log(`Router.beforeEach => from=${from.path}, to=${to.path}`);

  // 可以设置页面的 title

  document.title = to.meta.title || '默认标题';

  // 执行下一个路由导航

  next();

});

 

router.afterEach((to, from) => {

  console.log(`Router.afterEach => from=${from.path}, to=${to.path}`);

});

 

// 路由独享

const router = new VueRouter({

  routes: [

    {

      path: '/foo',

      component: Foo,

      beforeEnter: (to, from, next) => {

        // 配置数组里针对单个路由的导航守卫

        console.log(`TestComponent route config beforeEnter => from=${from.path}, to=${to.path}`);

        next();

      },

    },

  ],

});

 

// 组件

const Foo = {

  template: `...`,

  beforeRouteEnter(to, from, next) {

    // 在渲染该组件的对应路由被 comfirm 前调用

    // 不!能!获取组件实例 this,因为当守卫执行前,组件实例还没被调用

  },

  beforeRouteUpdate(to, from, next) {

    // 在当前路由改变,但是该组件被复用时调用

    // 举个例子来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候

    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用

    // 可以访问组件实例 this

  },

  beforeRouteLeave(to, from, next) {

    // 导航离开该组件的对应路由时调用

    // 可以访问组件实例 this

  },

};

next 必须调用:

7、导航守卫执行顺序(面试!!!)

8、滚动行为(面试!!!)

vue-router 里面,怎么记住前一个页面的滚动条的位置???

使用前端路由,当切换到新路由时,想要页面滚动到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。

Vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。

【注意】:这个功能只在支持 history.pushState 的浏览器中可用。

scrollBehavior 生效的条件:

1

2

3

window.history.back(); // 后退

window.history.forward(); // 前进

window.history.go(-3); // 接收 number 参数,后退 N 个页面

举个例子:

1

2

3

4

5

6

7

8

9

10

11

12

// 1. 记住:手动点击浏览器返回或者前进按钮,记住滚动条的位置,基于 history API 的,其中包括:go、back、forward、手动点击浏览器返回或者前进按钮

// 2. 没记住:router-link,并没有记住滚动条的位置

 

const router = new VueRouter({

  mode: 'history',

  base: process.env.BASE_URL,

  routes,

  scrollBehavior: (to, from, savedPosition) => {

    console.log(savedPosition); // 已保存的位置信息

    return savedPosition;

  },

});

9、路由懒加载

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

举个例子:

1

2

3

4

5

const Foo = () => import(/* webpackChunkName: "foo" */ './Foo.vue');

 

const router = new VueRouter({

  routes: [{ path: '/foo', component: Foo }],

});

原文链接:https://juejin.cn/post/7142051974383419428
相关文章
最新更新