组件通信
每个组件都有自己的数据, 提供在data中, data是一个函数, 保证每个组件的数据是独立的
封装成组件, 数据没法玩了~
组件的通讯:
父传子
子传父
非父子
组件通信 - 父传子
需求: 就想要在子组件中使用根组件的msg
每个组件都有自己的数据
- 子组件通过
prop
属性接收 - 父组件通过给子组件加属性方式传值
todos 列表渲染练习 - 父传子
子组件中 希望能用 父组件的东西
- 子组件通过 props 接收
- 父组件通过给 子组件添加标签自定义属性的传值
区分 props 和 data
组件中的数据来源, data props
data自己提供的数据,可以随意更改
props 父组件传递过来的数据, 不允许修改
不要改父组件传递过来的值,是在遵循一个开发规范 => 单向数据流
单向数据流:父组件的数据如果修改了,会自动向下流动,影响到子组件,触发子组件的更新
代码演示:
<style>
#app {
width: 500px;
height: 500px;
background-color: pink;
}
.son {
width: 300px;
height: 300px;
background-color: skyblue;
}
</style>
</head>
<body>
<div id="app">
<h1>我是父组件</h1>
<son :msg="msg"></son>
</div>
<script src="./vue.js"></script>
<script>
// 组件的数据来源:
// 1. data 自己提供的数据, 可以随便改
// 2. props 父组件传递过来的数据, 不要改
// 不要改父组件传递过来的值, 是在遵循一个开发规范 => 单向数据流
// 单向数据流: 父组件的数据如果修改了, 会自动向下流动, 影响到子组件, 触发子组件的更新
Vue.component('son', {
template: `
<div class="son">
<h3>我是son组件</h3>
<p>父组件传递过来的数据: {{ msg }}</p>
<p>自己的数据: {{ car }}</p>
<button @click="fn">改值</button>
</div>
`,
data () {
return {
car: '小车车'
}
},
methods: {
fn () {
// 修改自己的数据
// this.car = '大车车'
// 修改父组件的数据
this.msg = '老王'
}
},
// props中的数据 和 data一样, 可以在组件中使用
props: ['msg']
})
const vm = new Vue({
el: '#app',
data: {
msg: 'hello vue'
}
})
</script>
</body>
组件通信 - 子传父
- 子组件可以通过出发事件的同事,传值。
this.$emit('事件名', 参数1, 参数2, ...) 触发事件的同时传参的
- 父组件给子组件注册一个对应的事件(事件名要跟对应的事件对应起来)
@事件名 = 'fatherFn'
代码演示:
<style>
#app {
width: 500px;
height: 500px;
background-color: pink;
}
.son {
width: 300px;
height: 300px;
background-color: skyblue;
}
</style>
</head>
<body>
<!-- 模板在谁的管理范围内, 用的就是谁的变量 -->
<div id="app">
<h3>我是父组件</h3>
<son @sb="fatherFn"></son>
</div>
<script src="./vue.js"></script>
<script>
// 子传父:
// 1. 在子组件中, 通过触发事件的同时, 传值
// this.$emit(事件名, 参数1, 参数2, .....)
// 2. 在父组件中, 给子组件注册对应的事件
Vue.component('son', {
template: `
<div class="son">
我是son组件 <button @click="fn">传值给父组件</button>
</div>
`,
data () {
return {
money: '100块'
}
},
methods: {
fn () {
this.$emit('sb', this.money, '小车车')
}
}
})
const vm = new Vue({
el: '#app',
data: {
msg: 'hello vue'
},
methods: {
fatherFn (money, str) {
console.log(1111)
console.log(money, str)
}
}
})
// 子传父:
// 1. 在子组件中, 通过触发事件的同时传值
// this.$emit('事件名', 参数1, ...)
// 2. 在父组件中, 给子组件去注册 对应的 事件
// @事件名 = 'fatherFn'
</script>
</body>
todos - 删除功能 - 子传父
注意: 事件名的规范, 所有的事件名都必须是小写
todos - 添加功能 - 子传父
todos - 数据持久化
todos - 修改 - 修改状态
父传子, 传递复杂类型的说明
- 如果传递的是简单数据类型, 修改了, vue会直接报错
- 如果传递的是复杂数据类型, 不要不修改地址, 就不会报错 (虽然不报错, 但是也避免),没有遵循单向数据流
代码演示:
<style>
#app {
width: 500px;
height: 500px;
background-color: pink;
}
.son {
width: 300px;
height: 300px;
background-color: skyblue;
}
</style>
</head>
<body>
<div id="app">
<h3>我是父组件</h3>
<son :msg="msg" :obj="obj"></son>
</div>
<script src="./vue.js"></script>
<script>
// 父传子:
// 1. 如果传递的是简单数据类型, 修改了, vue会直接报错
// 2. 如果传递的是复杂数据类型, 不要不修改地址, 就不会报错 (虽然不报错, 但是也避免)
// 没有遵循单向数据流
Vue.component('son', {
template: `
<div class="son">
<h3>我是son组件 <button @click="fn">改值</button></h3>
<p>{{ msg }}</p>
<p>{{ obj.name }}</p>
<p>{{ obj.age }}</p>
</div>
`,
props: ['msg', 'obj'],
methods: {
fn () {
// this.msg = '嘿嘿'
this.obj.name = '老花'
}
}
})
const vm = new Vue({
el: '#app',
data: {
msg: 'hello vue',
obj: {
name: '小花',
age: 18
}
}
})
</script>
</body>
todos - 修改 - 修改内容
todos - 底部计算属性完成 - 父传子
todos - 清空所有完成
todos案例
源代码已上传至资源,文件名叫:Vue第7天-todos
组件化开发
组件通讯-非父子
非父子组件之间通过一个空的Vue实例来传递数据。
(1) 先创建一个 都能访问到的 事件总线(event bus) 实际就是一个空的vue实例
(2) 在 A 组件中, 触发 bus 的事件, 触发事件的同时传值
bus.$emit(事件名, 参数1, 参数2, ....)
(3) 在 B 组件中, 给 bus 注册对应的事件, 接收参数
bus.$on(事件名, 事件处理函数)
const bus = new Vue(); //bus:公交车 事件总线
- 核心逻辑
组件A给组件B传值:
1. 创建一个bus对象
2. 组件a触发事件,给组件b发送数据
3. 组件b给bus对象注册事件
4. 组件b提供函数,用于接收数据
- 组件A触发bus的事件
<button @click="send">表白</button>
methods: {
send() {
bus.$emit("get", this.msg);
}
}
- 组件B给bus注册对应的事件
//rose在组件创建的时候,给bus注册了一个事件
created () {
bus.$on("get", (msg)=>{
console.log("这是rose注册的事件", msg);
this.msg = msg;
});
}
- 组件B通过事件处理程序可以获取到传递的值
bus.$on("get", (msg)=>{
console.log("这是rose注册的事件", msg);
this.msg = msg;
});
注意点:1. 必须是同一辆公交车 2. 注册的事件和触发的事件必须保持一致
代码演示:
<style>
.jack, .rose {
width: 250px;
height: 250px;
background-color: pink;
margin: 20px;
border: 3px solid #000;
}
</style>
</head>
<body>
<div id="app">
<jack></jack>
<rose></rose>
</div>
<script src="./vue.js"></script>
<script>
// jack 和 rose 两个组件, jack想跟rose说一句话
// 非父子通信
// (1) 先创建一个 都能访问到的 事件总线(event bus) 实际就是一个空的vue实例
// (2) 在 A 组件中, 触发 bus 的事件, 触发事件的同时传值
// bus.$emit(事件名, 参数1, 参数2, ....)
// (3) 在 B 组件中, 给 bus 注册对应的事件, 接收参数
// bus.$on(事件名, 事件处理函数)
const bus = new Vue()
Vue.component('jack', {
template: `
<div class="jack">
<h3>我是jack</h3>
<button @click="fn">对rose说</button>
<p>{{ info }}</p>
</div>
`,
data () {
return {
msg: 'you jump, i look look',
info: ''
}
},
created () {
bus.$on('rose-reply', (info) => {
this.info = info
})
},
methods: {
fn () {
// console.log('希望给rose传参')
bus.$emit('jack-say', this.msg)
}
}
})
Vue.component('rose', {
template: `
<div class="rose">
<h3>我是rose</h3>
<button @click="fn">rose回答</button>
<p>{{ info }}</p>
</div>
`,
data () {
return {
info: ''
}
},
// bus的事件注册的越早越好, 因为只有注册了事件, 将来才能触发到
created () {
bus.$on('jack-say', (msg) => {
this.info = msg
})
},
methods: {
fn () {
// 触发bus的事件的同时, 传值
bus.$emit('rose-reply', 'get out!! 你个渣男~')
}
}
})
const vm = new Vue({
el: '#app',
data: {
msg: 'hello vue'
}
})
</script>
案例:开关灯案例
<style>
.light {
width: 100px;
height: 100px;
border-radius: 50%;
text-align: center;
line-height: 100px;
margin: 0 auto;
color: #fff;
background-color: rgb(17, 4, 4);
}
/* 灯座 */
.bottom {
width: 150px;
height: 50px;
margin-top: 10px;
line-height: 50px;
text-align: center;
color: #fff;
background-color: #000;
}
.container {
width: 150px;
}
.active {
background-color: #ff0;
color: #000;
}
</style>
</head>
<body>
<div id="app" class="container">
<light-pao></light-pao>
<light-zuo></light-zuo>
</div>
<script src="vue.js"></script>
<script>
const bus = new Vue()
// 灯泡组件
Vue.component('light-pao', {
template: `
<div class="light" :class="{ active: isOn }">我是一盏灯</div>
`,
data () {
return {
isOn: false
}
},
created () {
bus.$on('turn', (flag) => {
this.isOn = flag
})
}
})
// 灯座组件
Vue.component('light-zuo', {
template: `
<div class="bottom">
<button @click="turn(true)">开灯</button>
<button @click="turn(false)">关灯</button>
</div>
`,
methods: {
turn (flag) {
// console.log(flag)
bus.$emit('turn', flag)
}
}
})
const vm = new Vue({
el: '#app',
data: {}
})
</script>
</body>
bus是一种通用的组件通讯方案
我们有三种组件通讯的方案
1. 父传子
2. 子传父
3. 非父子(bus)
其实bus方案也适用于父传子和子传父的方案
slot插槽
前置说明
<style>
.modal {
width: 400px;
padding: 30px;
border: 3px solid #000;
border-radius: 10px;
margin: 10px;
}
</style>
</head>
<body>
<div id="app">
<modal title="警告"></modal>
<modal title="危险"></modal>
</div>
<script src="./vue.js"></script>
<script>
// 封装一个对话框组件,
// 父传子, 只能配置一些简单的结构, 如果大段的内容需要用户定制, 父传子就不好实现了
// 需求: 上面的对话框内容, 要求是 textarea, 下面对话框的内容, 要求是 p 段落
// 复杂的组件定制, 需要学习插槽
Vue.component('modal', {
template: `
<div class="modal">
<div class="header">
<h3>{{ title }}</h3>
</div>
<div class="main">
<p>亲! 你确认要退出本系统么?</p>
</div>
<div class="footer">
<button>确认</button>
<button>取消</button>
</div>
</div>
`,
props: ['title']
})
const vm = new Vue({
el: '#app',
data: {
msg: 'hello vue'
}
})
</script>
内容分发,可以将组件使用时,内容写的内容,分发到slot标签
当组件中某一项需要单独定义,那么就应该使用solt
Vue 实现了一套内容分发的 API,将
<slot>
元素作为承载分发内容的出口。
slot插槽:内容分发,可以将组件使用时,内容写得内容,分发到slot标签
- 匿名插槽(默认插槽):没有配置过名字的插槽,只要是没有具体分配的内容,都会给到匿名插槽
- 具名插槽:给插槽起个名字,可以实现定向分发,不是给他的他不要
- 给插槽取名
- 需要用
template
标签包裹内容 - 通过
v-slot: 插槽名
制定分发给谁
slot插槽基本语法
<style>
.modal {
width: 400px;
padding: 30px;
border: 3px solid #000;
border-radius: 10px;
margin: 10px;
}
</style>
</head>
<body>
<div id="app">
<modal title="警告">
<textarea name="" id="" cols="30" rows="10"></textarea>
</modal>
<modal title="危险">
<p>我是一个段落</p>
</modal>
<modal title="温馨">
爱好:
<input type="checkbox">篮球
<input type="checkbox">足球
<input type="checkbox">保龄球
</modal>
</div>
<script src="./vue.js"></script>
<script>
// 封装一个对话框组件,
// 父传子, 只能配置一些简单的结构, 如果大段的内容需要用户定制, 父传子就不好实现了
// 需求: 上面的对话框内容, 要求是 textarea, 下面对话框的内容, 要求是 p 段落
// 复杂的组件定制, 需要学习插槽
// slot插槽: 内容分发, 可以将组件使用时, 内部写的内容, 分发到 slot 标签
Vue.component('modal', {
template: `
<div class="modal">
<div class="header">
<h3>{{ title }}</h3>
</div>
<div class="main">
<slot></slot>
</div>
<div class="footer">
<button>确认</button>
<button>取消</button>
</div>
</div>
`,
props: ['title']
})
const vm = new Vue({
el: '#app',
data: {
msg: 'hello vue'
}
})
</script>
</body>
单个slot
除非子组件模板包含至少一个
<slot>
插口,否则父组件的内容将会被丢弃 ,当子组件模板只有一个没有属性的 slot 时,父组件整个内容片段将插入到 slot 所在的 DOM 位置,并替换掉 slot 标签本身。
在组件的模版中定义slot
插槽
Vue.component("modal", {
template: `
<div class="modal">
<p>温馨提示</p>
<div>
<slot></slot>
</div>
<button>关闭</button>
</div>
`,
});
父组件传值
<modal>你确定要退出系统吗?</modal>
<modal>你确定要删除这个内容吗?</modal>
具名插槽
如果一个组件中想使用多个slot那么此时就应该使用具名slot。
Vue.component("modal", {
template: `
<div class="modal">
<slot name="header"></slot>
<slot name="content"></slot>
<slot name="footer"></slot>
</div>
`,
});
<modal>
<p slot="header">温馨提示</p>
<div slot="content">你要删除内容吗</div>
<button slot="footer">关闭</button>
</modal>
<modal>
<p slot="header">提示</p>
<div slot="content">你要删除这条记录吗</div>
<button slot="footer">开启</button>
</modal>
// 封装一个对话框组件,
// 父传子, 只能配置一些简单的结构, 如果大段的内容需要用户定制, 父传子就不好实现了
// 需求: 上面的对话框内容, 要求是 textarea, 下面对话框的内容, 要求是 p 段落
// 复杂的组件定制, 需要学习插槽
// slot插槽: 内容分发, 可以将组件使用时, 内部写的内容, 分发到 slot 标签
// 1. 匿名插槽(默认插槽): 没有配置过名字的插槽, 没有具体分配的内容, 都会给到匿名插槽
// 2. 具名插槽: 给插槽起个名字, 可以实现定向分发, 不是给他的他不要
// (1) 给插槽起个名字 <slot name="main"></slot>
// (2) 在分发内容时, 使用 template标签, 包裹需要分发的内容
// (3) 在template标签上, 通过 v-slot:插槽名 指定分发给谁
<style>
.modal {
width: 400px;
padding: 30px;
border: 3px solid #000;
border-radius: 10px;
margin: 10px;
}
</style>
</head>
<body>
<div id="app">
<modal title="警告">
<template v-slot:main>
<textarea name="" id="" cols="30" rows="10"></textarea>
</template>
<template v-slot:footer>
<button>是</button>
<button>否</button>
</template>
</modal>
<modal title="温馨一波">
<template v-slot:main>
<p>段落</p>
<p>段落</p>
</template>
<template v-slot:footer>
<button>确认</button>
</template>
</modal>
</div>
<script src="./vue.js"></script>
<script>
// 封装一个对话框组件,
// 父传子, 只能配置一些简单的结构, 如果大段的内容需要用户定制, 父传子就不好实现了
// 需求: 上面的对话框内容, 要求是 textarea, 下面对话框的内容, 要求是 p 段落
// 复杂的组件定制, 需要学习插槽
// slot插槽: 内容分发, 可以将组件使用时, 内部写的内容, 分发到 slot 标签
// 1. 匿名插槽(默认插槽): 没有配置过名字的插槽, 没有具体分配的内容, 都会给到匿名插槽
// 2. 具名插槽: 给插槽起个名字, 可以实现定向分发, 不是给他的他不要
// (1) 给插槽起个名字 <slot name="main"></slot>
// (2) 在分发内容时, 使用 template标签, 包裹需要分发的内容
// (3) 在template标签上, 通过 v-slot:插槽名 指定分发给谁
Vue.component('modal', {
template: `
<div class="modal">
<div class="header">
<h3>{{ title }}</h3>
</div>
<div class="main">
<slot name="main"></slot>
</div>
<div class="footer">
<slot name="footer"></slot>
</div>
</div>
`,
props: ['title']
})
const vm = new Vue({
el: '#app',
data: {
msg: 'hello vue'
}
})
</script>
</body>