一、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 实例,叫做事件总线 EventBus
let 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>