一、sync修饰符和事件简写
1.1 sync修饰符是什么?
父子组件通信时,子组件向父组件传递数据需要在父组件监听事件,在子组件触发事件。Vue 为了简化处理这一过程,提供了 sync 修饰符和事件的简化处理;
1.2 如何使用sync修饰符
- 父组件在使用子组件时,在 prop 后面增加 .sync 修饰符,取消监听事件;
 - 子组件触发事件时,事件名写 update:prop 属性名
 
1.3 示例代码
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><div id="app"><!--在 prop 后面添加 .sync 修饰符,在子组件中 emit update:prop--><child :money.sync="money"></child></div><script src="vue.js"></script><script>let child = {data() {return {}},props: ['money'],template: `<div>儿子:{{money}} <button @click="fn">多要钱</button></div>`,methods: {fn() {this.$emit('update:money', 8888)}}};let vm = new Vue({el: '#app',data: {money: 250},components: {child}})</script></body></html>
二、父子通信练习之——模态框
2.1 需求详情
- 用 Vue 写一个模态框作为子组件,父组件有一个按钮可以,点击打开模态框。
 - 模态框中有一个关闭按钮,点击关闭则关闭模态框
 
2.2 示例代码
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title><style>* {margin: 0;padding: 0;}#app {width: 100vw;height: 100vh;}.modal {display: flex;justify-content: center;align-items: center;position: fixed;width: 100vw;height: 100vh;top: 0;left: 0;background: rgba(0, 0, 0, .3);}.modal .mask {width: 400px;height: 300px;padding: 20px;background: lightgreen;}</style></head><body><div id="app"><button @click="openModal">打开</button><modal :open="flag" @close="closeModal"></modal></div><template id="modalTpl"><div class="modal" v-show="open"><div class="mask"><button @click="shutdown">关闭</button></div></div></template><script src="vue.js"></script><script>let modal = {template: '#modalTpl',data() {return {}},props: ['open'],methods: {shutdown() {this.$emit('close', false);}}};let vm = new Vue({el: '#app',data: {flag: false},methods: {openModal() {this.flag = true;},closeModal() {this.flag = false;}},components: {modal}})</script></body></html>
三、父子组件的 mounted 问题
父子组件都有 mounted,谁的先执行?
如果父子组件都有 mounted,会先执行子组件的 mounted,然后再触发父组件的 mounted;目的是为了方便父组件可以获取子组件的实例;
示例代码
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><div id="app"><child ref="b"></child></div><template id="tpl"><div v-show="flag"><ul ref="a"><li v-for="(item, index) in arr">{{item}}</li></ul></div></template><script src="vue.js"></script><script>let child = {template: '#tpl',data() {return {arr: [1, 3, 5],flag: true}},mounted() {console.log('x');},methods: {hide() {this.flag = false;}}};let vm = new Vue({el: '#app',data: {},mounted() {console.log('y');父子组件都有 mounted,谁的先执行?如果父子组件都有 mounted,会先执行子组件的 mounted,然后再触发父组件的 mounted;目的是为了方便父组件可以获取子组件的实例console.log(this.$refs.b); ref 如果添加到原生 DOM 上,获得就是原生 DOM 元素,如果写在组件上,获取的就是组件实例的一个引用;可以获取子组件的数据,甚至调用子组件的方法this.$refs.b.hide();},components: {child}})</script></body></html>
四、动态组件
4.1 动态组件
有的时候,在不同组件之间进行动态切换是非常有用的,此时就需要用到动态组件;
4.2 实现动态组件
动态组件,需要使用内置的 component 组件     
在这个组件上有一个 is 属性,动态绑定该属性,当被绑定的值发生变化时,Vue会渲染 is 最新的值对应的组件;
4.3 示例
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><div id="app"><label>HOME <input type="radio" value="home" v-model="title"></label><label>List <input type="radio" value="list" v-model="title"></label><component :is="title"></component></div><script src="vue.js"></script><script>创建组件动态组件在进行切换时,会将上一个组件销毁,然后再挂载最新的组件动态组件,需要使用内置的 component 组件 // <component :is="title"></component>在这个组件上有一个 is 属性,动态绑定该属性,当被绑定的值发生变化时,Vue 会渲染 is 最新的值对应的组件;let home = {template: `<div>HOME <input type="text" v-model="home"></div>`,data() {return {home: ''}},mounted() {console.log('挂载home')},destroyed() {console.log('home销毁')}};let list = {template: `<div>LIST <input type="text" v-model="list"></div>`,data() {return {list: ''}},mounted() {console.log('list挂载')},destroyed() {console.log('list销毁')}};let vm = new Vue({el: '#app',data: {title: 'home'},components: {home,list}})</script></body></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 示例代码
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><div id="app"><prev></prev><next></next></div><script src="vue.js"></script><script>// 每个组件都是一个 Vue的实例,相互之间不能互通数据;// 要修改数据一定要通知,所以找一个第三方,让第三方监听事件,在事件触发时执行对应的修改数据的操作;// eventBus.$on(自定义事件名, 事件函数) 监听事件// eventBus.$emit(事件名) 触发事件let eventBus = new Vue(); // 这第三方就是一个空的 Vue 实例,叫做事件总线 EventBuslet prev = {data() {return {color: 'green'}},created() {eventBus.$on('changeRed', this.toRed) // 监听 changeRed 事件,当事件触发时,会执行 this.toRed 方法},methods: {toRed(x) {console.log(x); x 是事件触发时,传递的数据this.color = 'red';}},template: `<div :style="{background: color}">{{color}}</div>`};let next = {data () {return {color: '红色'}},methods: {red() {eventBus.$emit('changeRed', 'hahaha')}},template: `<div><button @click="red">变红</button></div>`};let vm = new Vue({el: '#app',data: {},components: {prev,next}});// 兄弟组件之间通信,通过 EventBus,谁的数据需要被改变,谁监听事件,谁发起改变谁触发事件;例如本例中,next 要修改 prev 的数据,所以在 prev 的 created 中 eventBus.$on() 监听事件,而 next 发起改变,所以在 next 中 $emit() 事件;// 哥哥改弟弟同理;</script></body></html>
七、插槽 slot
7.1 插槽是什么?
当引用组件时,我们可以向组件的标签中嵌入内容。这些内容可以嵌入到子组件中,但是需要使用插槽机制即,slot;
7.2 如何使用插槽
- 首先在创建子组件时需要声明一个 slot 标签占位;
 - 在组件标签中嵌入内容
 
7.3 具名slot和匿名slot
- 匿名 slot,在声明 slot 时不写 name 属性,嵌入在组件标签中没有写 slot 属性的标签都会放在匿名 slot 中
 - 具名 slot,在声明 slot 时要写上 name 属性;嵌入在组件标签中的内容需要指定 slot=“slot的名字”
 
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><div id="app"><panel><div>这是默认的内容</div><p>hahahah </p><div slot="header">这是个头</div><div slot="body">主体</div><div slot="footer">尾部</div></panel></div><template id="tpl"><div><slot></slot><slot name="header"></slot><slot name="body"></slot><slot name="footer"></slot></div></template><script src="vue.js"></script><script>let panel = {template: '#tpl',data() {return {x: 1}}};let vm = new Vue({el: '#app',data: {},components: {panel}});// 如果要想子组件中嵌入内容,需要使用插槽 slot;并且需要在子组件中提前用 slot 标签占位;// slot 分为匿名 slot 和具名 slot</script></body></html>
八、Vue 的路由 vue-router
8.1 路由和前端路由
- 路由:根据不同路径,执行不同操作;
 - 前端路由:单页面应用中由前端控制 url,实现页面的切换的效果(其实是在切换组件);
 
vue-router 实现页面的切换,单页面应用(SPA)页面切换页面的效果是通过在一个页面内,根据不同的 url 切换不同的组件实现的;
8.2 使用 vue-router
使用 vue-router 需要使用以下组件:
首页 是一个自定义标签,用于切换路由,点击它页面的 url 会切换,如果有匹配的组件,会把组件渲染到 router-view用于展示当前路由对应的组件 - 配置路由映射表,即路由和组件的对应关系;
 - 创建 vue-router 实例
 - 给 Vue 实例配置 router 属性
 
8.3 示例代码
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><div id="app"><div><router-link to="home" tag="button">首页</router-link><router-link to="list" tag="button">首页</router-link><!--to 是 router-link 的一个必须属性--><!--router-link 是一个自定义标签,用于切换路由--><!--router tag 属性,属性值是一个标签名,会把 router-link 解析成一个对应的标签--></div><!--router-view 是一个全局组件,用于显示当前路由对应的组件内容--><router-view></router-view></div><script src="vue.js"></script><script src="vue-router.js"></script><script>// 路由:根据不同路径,执行不同操作;// 前端路由:单页面应用中由前端控制url,实现页面的切换(其实是在切换组件);// vue-router 实现页面的切换,单页面应用(SPA)页面切换页面的效果是通过在一个页面内,根据不同的 url 切换不同的组件实现的;// 1. 创建组件// 2. 配置路由映射表// 3. 把路由映射表传给 VueRouter 进行注册;let home = {template: '<div>首页</div>'};let list = {template: '<div>列表页</div>'};// 配置路由映射表let routes = [{path: '/',component: home},{path: '/home',component: home},{path: '/list',component: list},{path: '*',component: home}];let router = new VueRouter({routes});let vm = new Vue({el: '#app',data: {},router})</script></body></html>
九、路由方法
除了使用 router-link 切换路由,vue-router 还提供了对应的方法用于切换路由;
9.1 常用的路由方法:
- this.$router.go(-1) 回退到上一个页面
 - this.$router.push(router|router-config) 跳转到指定路由
 
9.2 示例代码
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title><style>.view {margin-top: 30px;}</style></head><body><div id="app"><router-link to="/home" tag="button">首页</router-link><router-link to="/list" tag="button">列表</router-link><div class="view"><router-view></router-view></div></div><script src="vue.js"></script><script src="vue-router.js"></script><script>let home = {template: `<div>首页 <button @click="fn">去列表页</button></div>`,methods: {fn() {// this.$router.push() 跳转到指定路由this.$router.push('/list');}}};let list = {template: `<div>列表页 <button @click="goBack">返回</button></div>`,methods: {goBack () {// 返回上一个页面this.$router.go(-1);}}};let routes = [{path: '/',component: home},{path: '/home',component: home},{path: '/list',component: list},{path: '*',component: home}];let router = new VueRouter({routes});let vm = new Vue({el: '#app',router})</script></body></html>
十、路由嵌套
10.1 嵌套的路由
我们经常见到,从当前页面还可以跳转当前页面的子页面,如:
- 当前页面的路由 /detail
 - profile子页面路由:/detail/profile
 - about子页面的路由:/detail/about
 
此时我们称 profile 和 about 是 detail 的嵌套路由或者是子路由;
10.2 如何实现嵌套路由
在路由映射表中配置 children 属性,children 就是子级路由、嵌套路由的意思;
// 2. 配置路由表let routes = [{path: '/',component: home},{// 一级路由必须带 /path: '/home',component: home},{path: '/detail',component: detail,children: [{// 子路由不带 / 如果加上 / 就是一级路由了// 写在 /detail 的 children 中,就是 /detail/profile 的意思path: 'profile',component: profile},{path: 'about',component: about}]}];
10.3 示例代码
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><div id="app"><!--如果没有 / 会默认在后面拼上当前的路径,需要回到根路径上,给路径加上 /--><router-link to="/home">首页</router-link><router-link to="/detail">详情页</router-link><router-view></router-view></div><template id="detail"><div><!--profile 和 about 是 detail 的子路由--><router-link to="/detail/profile">个人中心</router-link><router-link to="/detail/about">关于我们</router-link><router-view></router-view><!--/detail 的子路由需要一个 router-view 渲染 --></div></template><script src="vue.js"></script><script src="vue-router.js"></script><script>// 创建组件let home = {template: `<div>首页</div>`};let detail = {template: `#detail`};let profile = {template: `<div>个人中心</div>`};let about = {template: `<div>关于我们</div>`};// 2. 配置路由表let routes = [{path: '/',component: home},{// 一级路由必须带 /path: '/home',component: home},{path: '/detail',component: detail,children: [{// 子路由不带 / 如果加上 / 就是一级路由了// 写在 /detail 的 children 中,就是 /detail/profile 的意思path: 'profile',component: profile},{path: 'about',component: about}]}];let router = new VueRouter({routes});let vm = new Vue({el: '#app',data: {},router});</script></body></html>
十一、路由参数
11.1 动态路由
vue-router 的路由可以像 Node.js 的动态路由一样,可以配置动态路由;例如 /text/:id/:a 此时 id 和 a 就是动态的路由;而我们真正访问路由时,如 /text/12/13 此时的12和13就称为动态路由的参数;
11.2 如何获取动态路由的参数
this.$route.params
11.3 示例代码
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><div id="app"><router-link to="/articles/1/a?name=mabin&age=18">文章1</router-link><router-link to="/articles/2/b">文章2</router-link><router-link to="/articles/3/c">文章3</router-link><button @click="go">走你</button><router-view></router-view></div><script src="vue.js"></script><script src="vue-router.js"></script><script>let articles = {created() {console.log(this.$route);// this.$route.params 存储了当前动态路由的参数console.log(this.$router);},name: 'articles',template: `<div>第{{$route.params.id}}</div>`};let routes = [{name: 'articles',path: '/articles/:id/:txt',component: articles}];let router = new VueRouter({routes});let vm = new Vue({el: '#app',data: {},router,methods: {go() {this.$router.push({name: 'articles',params: {txt: 'x',id: 6},query: {name: 'zhangsan',age: 18}})}}})</script></body></html>
【发上等愿,结中等缘,享下等福,择高处立,寻平处住,向宽处行】
