一、sync修饰符和事件简写

1.1 sync修饰符是什么?

父子组件通信时,子组件向父组件传递数据需要在父组件监听事件,在子组件触发事件。Vue 为了简化处理这一过程,提供了 sync 修饰符和事件的简化处理;

1.2 如何使用sync修饰符

  • 父组件在使用子组件时,在 prop 后面增加 .sync 修饰符,取消监听事件;
  • 子组件触发事件时,事件名写 update:prop 属性名

1.3 示例代码

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <div id="app">
  9. <!--在 prop 后面添加 .sync 修饰符,在子组件中 emit update:prop-->
  10. <child :money.sync="money"></child>
  11. </div>
  12. <script src="vue.js"></script>
  13. <script>
  14. let child = {
  15. data() {
  16. return {}
  17. },
  18. props: ['money'],
  19. template: `<div>儿子:{{money}} <button @click="fn">多要钱</button></div>`,
  20. methods: {
  21. fn() {
  22. this.$emit('update:money', 8888)
  23. }
  24. }
  25. };
  26. let vm = new Vue({
  27. el: '#app',
  28. data: {
  29. money: 250
  30. },
  31. components: {
  32. child
  33. }
  34. })
  35. </script>
  36. </body>
  37. </html>

二、父子通信练习之——模态框

2.1 需求详情

  • 用 Vue 写一个模态框作为子组件,父组件有一个按钮可以,点击打开模态框。
  • 模态框中有一个关闭按钮,点击关闭则关闭模态框

2.2 示例代码

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. <style>
  7. * {
  8. margin: 0;
  9. padding: 0;
  10. }
  11. #app {
  12. width: 100vw;
  13. height: 100vh;
  14. }
  15. .modal {
  16. display: flex;
  17. justify-content: center;
  18. align-items: center;
  19. position: fixed;
  20. width: 100vw;
  21. height: 100vh;
  22. top: 0;
  23. left: 0;
  24. background: rgba(0, 0, 0, .3);
  25. }
  26. .modal .mask {
  27. width: 400px;
  28. height: 300px;
  29. padding: 20px;
  30. background: lightgreen;
  31. }
  32. </style>
  33. </head>
  34. <body>
  35. <div id="app">
  36. <button @click="openModal">打开</button>
  37. <modal :open="flag" @close="closeModal"></modal>
  38. </div>
  39. <template id="modalTpl">
  40. <div class="modal" v-show="open">
  41. <div class="mask">
  42. <button @click="shutdown">关闭</button>
  43. </div>
  44. </div>
  45. </template>
  46. <script src="vue.js"></script>
  47. <script>
  48. let modal = {
  49. template: '#modalTpl',
  50. data() {
  51. return {}
  52. },
  53. props: ['open'],
  54. methods: {
  55. shutdown() {
  56. this.$emit('close', false);
  57. }
  58. }
  59. };
  60. let vm = new Vue({
  61. el: '#app',
  62. data: {
  63. flag: false
  64. },
  65. methods: {
  66. openModal() {
  67. this.flag = true;
  68. },
  69. closeModal() {
  70. this.flag = false;
  71. }
  72. },
  73. components: {
  74. modal
  75. }
  76. })
  77. </script>
  78. </body>
  79. </html>

三、父子组件的 mounted 问题

父子组件都有 mounted,谁的先执行?

如果父子组件都有 mounted,会先执行子组件的 mounted,然后再触发父组件的 mounted;目的是为了方便父组件可以获取子组件的实例;

示例代码

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <div id="app">
  9. <child ref="b"></child>
  10. </div>
  11. <template id="tpl">
  12. <div v-show="flag">
  13. <ul ref="a">
  14. <li v-for="(item, index) in arr">{{item}}</li>
  15. </ul>
  16. </div>
  17. </template>
  18. <script src="vue.js"></script>
  19. <script>
  20. let child = {
  21. template: '#tpl',
  22. data() {
  23. return {
  24. arr: [1, 3, 5],
  25. flag: true
  26. }
  27. },
  28. mounted() {
  29. console.log('x');
  30. },
  31. methods: {
  32. hide() {
  33. this.flag = false;
  34. }
  35. }
  36. };
  37. let vm = new Vue({
  38. el: '#app',
  39. data: {},
  40. mounted() {
  41. console.log('y');
  42. 父子组件都有 mounted,谁的先执行?
  43. 如果父子组件都有 mounted,会先执行子组件的 mounted,然后再触发父组件的 mounted;目的是为了方便父组件可以获取子组件的实例
  44. console.log(this.$refs.b); ref 如果添加到原生 DOM 上,获得就是原生 DOM 元素,如果写在组件上,获取的就是组件实例的一个引用;可以获取子组件的数据,甚至调用子组件的方法
  45. this.$refs.b.hide();
  46. },
  47. components: {
  48. child
  49. }
  50. })
  51. </script>
  52. </body>
  53. </html>

四、动态组件

4.1 动态组件

有的时候,在不同组件之间进行动态切换是非常有用的,此时就需要用到动态组件;

4.2 实现动态组件

动态组件,需要使用内置的 component 组件
在这个组件上有一个 is 属性,动态绑定该属性,当被绑定的值发生变化时,Vue会渲染 is 最新的值对应的组件;

4.3 示例

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <div id="app">
  9. <label>HOME <input type="radio" value="home" v-model="title"></label>
  10. <label>List <input type="radio" value="list" v-model="title"></label>
  11. <component :is="title"></component>
  12. </div>
  13. <script src="vue.js"></script>
  14. <script>
  15. 创建组件
  16. 动态组件在进行切换时,会将上一个组件销毁,然后再挂载最新的组件
  17. 动态组件,需要使用内置的 component 组件 // <component :is="title"></component>
  18. 在这个组件上有一个 is 属性,动态绑定该属性,当被绑定的值发生变化时,Vue 会渲染 is 最新的值对应的组件;
  19. let home = {
  20. template: `<div>HOME <input type="text" v-model="home"></div>`,
  21. data() {
  22. return {
  23. home: ''
  24. }
  25. },
  26. mounted() {
  27. console.log('挂载home')
  28. },
  29. destroyed() {
  30. console.log('home销毁')
  31. }
  32. };
  33. let list = {
  34. template: `<div>LIST <input type="text" v-model="list"></div>`,
  35. data() {
  36. return {
  37. list: ''
  38. }
  39. },
  40. mounted() {
  41. console.log('list挂载')
  42. },
  43. destroyed() {
  44. console.log('list销毁')
  45. }
  46. };
  47. let vm = new Vue({
  48. el: '#app',
  49. data: {
  50. title: 'home'
  51. },
  52. components: {
  53. home,
  54. list
  55. }
  56. })
  57. </script>
  58. </body>
  59. </html>

五、keep-alive

5.1 什么是 keep-alive

component 虽然可以绑定动态组件,但是在组件切换时,原来的组件就会被销毁,数据会丢失,为了解决这个问题,我们使用 keep-alive;

包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。

六、事件总线 EventBus

6.1 什么是事件总线?

  • 每个组件都是一个 Vue 的实例,相互之间不能互通数据;
  • 要修改数据一定要通知,所以找一个第三方,让第三方监听事件,在事件触发时执行对应的修改数据的操作,这个第三方就是事件总线;

6.2 事件总线的用法

  • 创建一个空的 Vue 实例;let eventBus = new Vue();
  • eventBus.$on(自定义事件名, 事件函数) 监听事件
  • eventBus.$emit(事件名) 触发事件

6.3 示例代码

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <div id="app">
  9. <prev></prev>
  10. <next></next>
  11. </div>
  12. <script src="vue.js"></script>
  13. <script>
  14. // 每个组件都是一个 Vue的实例,相互之间不能互通数据;
  15. // 要修改数据一定要通知,所以找一个第三方,让第三方监听事件,在事件触发时执行对应的修改数据的操作;
  16. // eventBus.$on(自定义事件名, 事件函数) 监听事件
  17. // eventBus.$emit(事件名) 触发事件
  18. let eventBus = new Vue(); // 这第三方就是一个空的 Vue 实例,叫做事件总线 EventBus
  19. let prev = {
  20. data() {
  21. return {
  22. color: 'green'
  23. }
  24. },
  25. created() {
  26. eventBus.$on('changeRed', this.toRed) // 监听 changeRed 事件,当事件触发时,会执行 this.toRed 方法
  27. },
  28. methods: {
  29. toRed(x) {
  30. console.log(x); x 是事件触发时,传递的数据
  31. this.color = 'red';
  32. }
  33. },
  34. template: `<div :style="{background: color}">{{color}}</div>`
  35. };
  36. let next = {
  37. data () {
  38. return {
  39. color: '红色'
  40. }
  41. },
  42. methods: {
  43. red() {
  44. eventBus.$emit('changeRed', 'hahaha')
  45. }
  46. },
  47. template: `<div><button @click="red">变红</button></div>`
  48. };
  49. let vm = new Vue({
  50. el: '#app',
  51. data: {},
  52. components: {
  53. prev,
  54. next
  55. }
  56. });
  57. // 兄弟组件之间通信,通过 EventBus,谁的数据需要被改变,谁监听事件,谁发起改变谁触发事件;例如本例中,next 要修改 prev 的数据,所以在 prev 的 created 中 eventBus.$on() 监听事件,而 next 发起改变,所以在 next 中 $emit() 事件;
  58. // 哥哥改弟弟同理;
  59. </script>
  60. </body>
  61. </html>

七、插槽 slot

7.1 插槽是什么?

当引用组件时,我们可以向组件的标签中嵌入内容。这些内容可以嵌入到子组件中,但是需要使用插槽机制即,slot;

7.2 如何使用插槽

  1. 首先在创建子组件时需要声明一个 slot 标签占位;
  2. 在组件标签中嵌入内容

7.3 具名slot和匿名slot

  1. 匿名 slot,在声明 slot 时不写 name 属性,嵌入在组件标签中没有写 slot 属性的标签都会放在匿名 slot 中
  2. 具名 slot,在声明 slot 时要写上 name 属性;嵌入在组件标签中的内容需要指定 slot=“slot的名字”
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <div id="app">
  9. <panel>
  10. <div>这是默认的内容</div>
  11. <p>hahahah </p>
  12. <div slot="header">这是个头</div>
  13. <div slot="body">主体</div>
  14. <div slot="footer">尾部</div>
  15. </panel>
  16. </div>
  17. <template id="tpl">
  18. <div>
  19. <slot></slot>
  20. <slot name="header"></slot>
  21. <slot name="body"></slot>
  22. <slot name="footer"></slot>
  23. </div>
  24. </template>
  25. <script src="vue.js"></script>
  26. <script>
  27. let panel = {
  28. template: '#tpl',
  29. data() {
  30. return {
  31. x: 1
  32. }
  33. }
  34. };
  35. let vm = new Vue({
  36. el: '#app',
  37. data: {},
  38. components: {
  39. panel
  40. }
  41. });
  42. // 如果要想子组件中嵌入内容,需要使用插槽 slot;并且需要在子组件中提前用 slot 标签占位;
  43. // slot 分为匿名 slot 和具名 slot
  44. </script>
  45. </body>
  46. </html>

八、Vue 的路由 vue-router

8.1 路由和前端路由

  • 路由:根据不同路径,执行不同操作;
  • 前端路由:单页面应用中由前端控制 url,实现页面的切换的效果(其实是在切换组件);

vue-router 实现页面的切换,单页面应用(SPA)页面切换页面的效果是通过在一个页面内,根据不同的 url 切换不同的组件实现的;

8.2 使用 vue-router

使用 vue-router 需要使用以下组件:

  1. 首页 是一个自定义标签,用于切换路由,点击它页面的 url 会切换,如果有匹配的组件,会把组件渲染到 router-view
  2. 用于展示当前路由对应的组件
  3. 配置路由映射表,即路由和组件的对应关系;
  4. 创建 vue-router 实例
  5. 给 Vue 实例配置 router 属性

8.3 示例代码

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <div id="app">
  9. <div>
  10. <router-link to="home" tag="button">首页</router-link>
  11. <router-link to="list" tag="button">首页</router-link>
  12. <!--to 是 router-link 的一个必须属性-->
  13. <!--router-link 是一个自定义标签,用于切换路由-->
  14. <!--router tag 属性,属性值是一个标签名,会把 router-link 解析成一个对应的标签-->
  15. </div>
  16. <!--router-view 是一个全局组件,用于显示当前路由对应的组件内容-->
  17. <router-view></router-view>
  18. </div>
  19. <script src="vue.js"></script>
  20. <script src="vue-router.js"></script>
  21. <script>
  22. // 路由:根据不同路径,执行不同操作;
  23. // 前端路由:单页面应用中由前端控制url,实现页面的切换(其实是在切换组件);
  24. // vue-router 实现页面的切换,单页面应用(SPA)页面切换页面的效果是通过在一个页面内,根据不同的 url 切换不同的组件实现的;
  25. // 1. 创建组件
  26. // 2. 配置路由映射表
  27. // 3. 把路由映射表传给 VueRouter 进行注册;
  28. let home = {
  29. template: '<div>首页</div>'
  30. };
  31. let list = {
  32. template: '<div>列表页</div>'
  33. };
  34. // 配置路由映射表
  35. let routes = [
  36. {
  37. path: '/',
  38. component: home
  39. },
  40. {
  41. path: '/home',
  42. component: home
  43. },
  44. {
  45. path: '/list',
  46. component: list
  47. },
  48. {
  49. path: '*',
  50. component: home
  51. }
  52. ];
  53. let router = new VueRouter({
  54. routes
  55. });
  56. let vm = new Vue({
  57. el: '#app',
  58. data: {},
  59. router
  60. })
  61. </script>
  62. </body>
  63. </html>

九、路由方法

除了使用 router-link 切换路由,vue-router 还提供了对应的方法用于切换路由;

9.1 常用的路由方法:

  1. this.$router.go(-1) 回退到上一个页面
  2. this.$router.push(router|router-config) 跳转到指定路由

9.2 示例代码

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. <style>
  7. .view {
  8. margin-top: 30px;
  9. }
  10. </style>
  11. </head>
  12. <body>
  13. <div id="app">
  14. <router-link to="/home" tag="button">首页</router-link>
  15. <router-link to="/list" tag="button">列表</router-link>
  16. <div class="view">
  17. <router-view></router-view>
  18. </div>
  19. </div>
  20. <script src="vue.js"></script>
  21. <script src="vue-router.js"></script>
  22. <script>
  23. let home = {
  24. template: `<div>首页 <button @click="fn">去列表页</button></div>`,
  25. methods: {
  26. fn() {
  27. // this.$router.push() 跳转到指定路由
  28. this.$router.push('/list');
  29. }
  30. }
  31. };
  32. let list = {
  33. template: `<div>列表页 <button @click="goBack">返回</button></div>`,
  34. methods: {
  35. goBack () {
  36. // 返回上一个页面
  37. this.$router.go(-1);
  38. }
  39. }
  40. };
  41. let routes = [
  42. {
  43. path: '/',
  44. component: home
  45. },
  46. {
  47. path: '/home',
  48. component: home
  49. },
  50. {
  51. path: '/list',
  52. component: list
  53. },
  54. {
  55. path: '*',
  56. component: home
  57. }
  58. ];
  59. let router = new VueRouter({
  60. routes
  61. });
  62. let vm = new Vue({
  63. el: '#app',
  64. router
  65. })
  66. </script>
  67. </body>
  68. </html>

十、路由嵌套

10.1 嵌套的路由

我们经常见到,从当前页面还可以跳转当前页面的子页面,如:

  • 当前页面的路由 /detail
  • profile子页面路由:/detail/profile
  • about子页面的路由:/detail/about

此时我们称 profile 和 about 是 detail 的嵌套路由或者是子路由;

10.2 如何实现嵌套路由

在路由映射表中配置 children 属性,children 就是子级路由、嵌套路由的意思;

  1. // 2. 配置路由表
  2. let routes = [
  3. {
  4. path: '/',
  5. component: home
  6. },
  7. {
  8. // 一级路由必须带 /
  9. path: '/home',
  10. component: home
  11. },
  12. {
  13. path: '/detail',
  14. component: detail,
  15. children: [
  16. {
  17. // 子路由不带 / 如果加上 / 就是一级路由了
  18. // 写在 /detail 的 children 中,就是 /detail/profile 的意思
  19. path: 'profile',
  20. component: profile
  21. },
  22. {
  23. path: 'about',
  24. component: about
  25. }
  26. ]
  27. }
  28. ];

10.3 示例代码

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <div id="app">
  9. <!--如果没有 / 会默认在后面拼上当前的路径,需要回到根路径上,给路径加上 /-->
  10. <router-link to="/home">首页</router-link>
  11. <router-link to="/detail">详情页</router-link>
  12. <router-view></router-view>
  13. </div>
  14. <template id="detail">
  15. <div>
  16. <!--profile 和 about 是 detail 的子路由-->
  17. <router-link to="/detail/profile">个人中心</router-link>
  18. <router-link to="/detail/about">关于我们</router-link>
  19. <router-view></router-view>
  20. <!--/detail 的子路由需要一个 router-view 渲染 -->
  21. </div>
  22. </template>
  23. <script src="vue.js"></script>
  24. <script src="vue-router.js"></script>
  25. <script>
  26. // 创建组件
  27. let home = {
  28. template: `<div>首页</div>`
  29. };
  30. let detail = {
  31. template: `#detail`
  32. };
  33. let profile = {
  34. template: `<div>个人中心</div>`
  35. };
  36. let about = {
  37. template: `<div>关于我们</div>`
  38. };
  39. // 2. 配置路由表
  40. let routes = [
  41. {
  42. path: '/',
  43. component: home
  44. },
  45. {
  46. // 一级路由必须带 /
  47. path: '/home',
  48. component: home
  49. },
  50. {
  51. path: '/detail',
  52. component: detail,
  53. children: [
  54. {
  55. // 子路由不带 / 如果加上 / 就是一级路由了
  56. // 写在 /detail 的 children 中,就是 /detail/profile 的意思
  57. path: 'profile',
  58. component: profile
  59. },
  60. {
  61. path: 'about',
  62. component: about
  63. }
  64. ]
  65. }
  66. ];
  67. let router = new VueRouter({
  68. routes
  69. });
  70. let vm = new Vue({
  71. el: '#app',
  72. data: {},
  73. router
  74. });
  75. </script>
  76. </body>
  77. </html>

十一、路由参数

11.1 动态路由

vue-router 的路由可以像 Node.js 的动态路由一样,可以配置动态路由;例如 /text/:id/:a 此时 id 和 a 就是动态的路由;而我们真正访问路由时,如 /text/12/13 此时的12和13就称为动态路由的参数;

11.2 如何获取动态路由的参数

this.$route.params

11.3 示例代码

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <div id="app">
  9. <router-link to="/articles/1/a?name=mabin&age=18">文章1</router-link>
  10. <router-link to="/articles/2/b">文章2</router-link>
  11. <router-link to="/articles/3/c">文章3</router-link>
  12. <button @click="go">走你</button>
  13. <router-view></router-view>
  14. </div>
  15. <script src="vue.js"></script>
  16. <script src="vue-router.js"></script>
  17. <script>
  18. let articles = {
  19. created() {
  20. console.log(this.$route);
  21. // this.$route.params 存储了当前动态路由的参数
  22. console.log(this.$router);
  23. },
  24. name: 'articles',
  25. template: `<div>第{{$route.params.id}}</div>`
  26. };
  27. let routes = [
  28. {
  29. name: 'articles',
  30. path: '/articles/:id/:txt',
  31. component: articles
  32. }
  33. ];
  34. let router = new VueRouter({
  35. routes
  36. });
  37. let vm = new Vue({
  38. el: '#app',
  39. data: {},
  40. router,
  41. methods: {
  42. go() {
  43. this.$router.push({
  44. name: 'articles',
  45. params: {
  46. txt: 'x',
  47. id: 6
  48. },
  49. query: {
  50. name: 'zhangsan',
  51. age: 18
  52. }
  53. })
  54. }
  55. }
  56. })
  57. </script>
  58. </body>
  59. </html>