前言
本章总结了vue2.x与vue3.x的通信方式
VUE2.x的通信方式
-
props
传递数据 - 通过
$emit
触发自定义事件 - 使用
$ref
- 作用域插槽(
slot
) eventBus
-
$parent
(或$root
)与$children
-
$attrs
与$listeners
-
provide
与inject
Vuex
适合父子间通信:props
、$emit
、$ref
、slot
、$parent
、$children
适合兄弟组件之间的通信:eventBus
、Vuex
祖孙与后代组件之间的通信:$attrs
/$listeners
、provide
/inject
、eventBus
、Vuex
复杂关系的组件之间的通信:Vuex
1. props
传递数据
- 适用场景:父组件传递数据给子组件
- 使用方式:
子组件设置props属性,定义接收父组件传递过来的参数
父组件在使用子组件标签中通过字面量来传递值
// 父组件
<Children name="jack" age=18 />
// 子组件
props:{
name:String // 接收的类型参数
age:{
type:Number, // 接收的类型为数值
defaule:18 // 默认值为18
}
}
2. $emit
触发自定义事件
- 适用场景:子组件传递数据给父组件
- 使用方式:
子组件通过$emit
触发自定义事件,$emit
第二个参数为传递的数值
父组件绑定监听器获取到子组件传递过来的参数
// 父组件
<Children @add="cartAdd" />
// 子组件
this.$emit('add', 'good')
3. ref
- 适用场景:父组件需要获取到子组件时
- 使用方式:
父组件在使用子组件的时候设置ref
父组件通过设置子组件ref来获取数据
// 父组件
<Children ref="foo" />
this.$refs.foo // 获取子组件实例,通过子组件实例我们就能拿到对应的数据及函数
4. 作用域插槽(slot
)
5. EventBus
- 适用场景:兄弟组件/隔代组件之间的通信
- 使用方式:
创建一个中央时间总线EventBus
一个组件通过$on
监听自定义事件
另一个组件通过$emit
触发事件,$emit
第二个参数为传递的数值
// 创建一个中央时间总线类
class Bus {
constructor() {
this.callbacks = {}; // 存放事件的名字
}
$on(name, fn) {
this.callbacks[name] = this.callbacks[name] || [];
this.callbacks[name].push(fn);
}
$emit(name, args) {
if (this.callbacks[name]) {
this.callbacks[name].forEach((cb) => cb(args));
}
}
}
// main.js
Vue.prototype.$bus = new Bus() // 将$bus挂载到vue实例的原型上
// 一个组件监听自定义事件
this.$bus.$on('foo', function() {});
// 另一个组件触发事件
this.$bus.$emit('foo')
6. $parent
或$root
与$children
- 适用场景:通过共同祖辈parent或者root搭建通信侨联
- 兄弟组件之间:
// 兄弟组件
this.$parent.on('add',this.add);
// 另一个兄弟组件
this.$parent.emit('add');
- 父子组件之间:
// 子组件获取到子组件
this.$parent
// 同理,父组件获取到子组件,但是是个列表
this.$children
7. $attrs
与 $listeners
- 适用场景:祖先传递属性(可以是值,可以是函数)给子孙
-
$attrs
:包含了父作用域中不被认为 (且不预期为) props 的特性绑定 (class 和 style 除外),并且可以通过v-bind=”$attrs”
传入内部组件。当一个组件没有声明任何 props 时,它包含所有父作用域的绑定 (class 和 style 除外)。 -
$listeners
:包含了父作用域中的 (不含 .native 修饰符) v-on 事件监听器。它可以通过v-on=”$listeners”
传入内部组件。它是一个对象,里面包含了作用在这个组件上的所有事件监听器,相当于子组件继承了父组件的事件。
<!-- father.vue 组件:-->
<template>
<child :name="name" :age="age" :infoObj="infoObj" @updateInfo="updateInfo" @delInfo="delInfo" />
</template>
<script>
import Child from '../components/child.vue'
export default {
name: 'father',
components: { Child },
data () {
return {
name: 'Lily',
age: 22,
infoObj: {
from: '上海',
job: 'policeman',
hobby: ['reading', 'writing', 'skating']
}
}
},
methods: {
updateInfo() {
console.log('update info');
},
delInfo() {
console.log('delete info');
}
}
}
</script>
<!-- child.vue 组件:-->
<template>
<grand-son :height="height" :weight="weight" @addInfo="addInfo" v-bind="$attrs" v-on="$listeners" />
// 通过 $listeners 将父作用域中的事件,传入 grandSon 组件,使其可以获取到 father 中的事件
</template>
<script>
import GrandSon from '../components/grandSon.vue'
export default {
name: 'child',
components: { GrandSon },
props: ['name'],
data() {
return {
height: '180cm',
weight: '70kg'
};
},
created() {
console.log(this.$attrs);
// 结果:age, infoObj, 因为父组件共传来name, age, infoObj三个值,由于name被 props接收了,所以只有age, infoObj属性
console.log(this.$listeners); // updateInfo: f, delInfo: f
},
methods: {
addInfo () {
console.log('add info')
}
}
}
</script>
<!-- grandSon.vue 组件:-->
<template>
<div>
{{ $attrs }} --- {{ $listeners }}
<div>
</template>
<script>
export default {
... ...
props: ['weight'],
created() {
console.log(this.$attrs); // age, infoObj, height
console.log(this.$listeners) // updateInfo: f, delInfo: f, addInfo: f
this.$emit('updateInfo') // 可以触发隔代组件father中的updateInfo函数
}
}
</script>
简易版例子:
// 给Grandson隔代传值,communication/index.vue
<Child2 msg="lalala" @some-event="onSomeEvent"></Child2>
// Child2做展开
<Grandson v-bind="$attrs" v-on="$listeners"></Grandson>
// Grandson使⽤
<div @click="$emit('some-event', 'msg from grandson')">
{{msg}}
</div>
8. provide
与 inject
- 适用场景:祖先传递值给子孙
// 祖先组件
provide(){
return {
foo:'foo'
}
}
// 后代组件
inject:['foo'] // 获取到祖先组件传递过来的值
9. vuex
- 适用场景: 复杂关系的组件数据传递
- Vuex作用相当于一个用来存储共享变量的容器
state
:包含了store中存储的各个状态。
getter
: 类似于 Vue 中的计算属性,根据其他 getter 或 state 计算返回值。
mutation
: 一组方法,是改变store中状态的执行者,只能是同步操作。
action
: 一组方法,其中可以包含异步操作。
// main.js
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0,
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getter: {
doneTodos: (state, getters) => {
return state.todos.filter(todo => todo.done)
}
},
mutations: {
increment (state, payload) {
state.count++
}
},
actions: {
addCount(context) {
// 可以包含异步操作
// context 是一个与 store 实例具有相同方法和属性的 context 对象
}
}
})
// 注入到根实例
new Vue({
el: '#app',
// 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
store,
template: '<App/>',
components: { App }
});
// 在子组件中使用
// 创建一个 Counter 组件
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count
},
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
}
}
VUE3.0的通信方式
-
props
传递数据 - 通过
emit
触发自定义事件 - 使用
ref
- 作用域插槽(
slot
) -
mitt
()eventBus
-
$parent
(或$root
)与$children
-
$attrs
与$listeners
-
provide
与inject
Vuex
适合父子间通信:props
、emit
、ref
、slot
、parent
、children
适合兄弟组件之间的通信:mitt
()、eventBus
Vuex
祖孙与后代组件之间的通信:$attrs
/$listeners
、provide
/inject
、mitt
()、eventBus
Vuex
复杂关系的组件之间的通信:Vuex
1. props
传递数据
- 适用场景:父组件向子组件传递数据
- 使用方式:
父组件在使用子组件标签中通过字面量来传递值(与VUE2.x方式相同)
与VUE2.x相同的是子组件也设置props属性配置接受的数据,不同的是vue3.0的prop为setup的第一个参数
// 父组件
<child msg="hello"/>
// 子组件
export default defineComponent({
props: { // 配置要接受的props
msg: {
type: String,
default: () => ''
}
},
setup(props) {
console.log('props ====', props);
},
});
2. emit
触发自定义事件
- 适用场景:子组件传递数据给父组件
- 使用方式:
父组件绑定监听器获取到子组件传递过来的参数(与VUE2.x方式相同)
与VUE2.x不同的是:子组件通过context.emit
触发自定义事件(context
为setup
的第二个参数,为上下文),相同的是:emit
第二个参数为传递的数值
<!-- Father.vue -->
<template>
<div style="width: 500px; height: 500px; padding: 20px; border: 2px solid #ff00ff;">
<div>Father:</div>
<child msg="hello" @todoSth="todoSth"/>
</div>
</template>
<script lang="ts">
import {
defineComponent
} from 'vue';
import child from './components/child.vue';
export default defineComponent({
components: {
child
},
setup() {
// 父组件绑定监听器获取到子组件传递过来的参数
const todoSth = (text: string) => {
console.log('text: ', text);
};
return {
todoSth
};
},
});
</script>
<!-- Child.vue -->
<template>
<div style="width: 100%; height: calc(100% - 40px); padding: 20px; box-sizing: border-box; border: 2px solid #00ff00;">
<div>Child:</div>
<button @click="setMsg2Parent">点击按钮给父组件传递信息</button>
</div>
</template>
<script lang="ts">
import {
defineComponent
} from 'vue';
export default defineComponent({
setup(props, context) {
const setMsg2Parent = () => {
context.emit('todoSth', 'hi parent!');
};
return {
setMsg2Parent
};
},
});
</script>
3. ref
- 适用场景:父组件需要获取到子组件时
- 使用方式:
父组件在使用子组件的时候设置ref(与VUE2.x方式相同)
父组件通过设置子组件ref来获取数据
注:
: 1.获取子组件时变量名必须与子组件的ref属性保持一致;2.必须将获取到的组件return。
<!-- Father.vue -->
<template>
<div style="width: 500px; height: 500px; padding: 20px; border: 2px solid #ff00ff;">
<div>Father:</div>
<button @click="getChildCom">获取到子组件并调用其属性</button>
<child ref="childCom" msg="hello"/>
</div>
</template>
<script lang="ts">
import {
defineComponent,
ref
} from 'vue';
import child from './components/child.vue';
export default defineComponent({
components: {
child
},
setup() {
const childCom = ref<HTMLElement>(null);
// 获取到子组件并调用其属性
const getChildCom = () => {
childCom.value.childText = '父组件想要改变这个属性!';
childCom.value.childMethod1();
};
return {
childCom,
getChildCom
};
},
});
</script>
<!-- Child.vue -->
<template>
<div style="width: 100%; height: calc(100% - 40px); padding: 20px; box-sizing: border-box; border: 2px solid #00ff00;">
<div>Child:</div>
<p>{{childText}}</p>
</div>
</template>
<script lang="ts">
import {
defineComponent,
reactive,
toRefs
} from 'vue';
export default defineComponent({
props: {
msg: {
type: String,
default: () => ''
}
},
setup(props, context) {
const currStatus = reactive({
childText: '我是一个子组件~~~'
});
const childMethod1 = () => {
console.log('这是子组件的一个方法');
};
return {
childMethod1,
...toRefs(currStatus)
};
},
});
</script>
事件调用前
事件调用后
4. 作用域插槽(slot
)
5. mitt
- 适用场景:兄弟组件/隔代组件之间的通信
- Vue 3 移除了
$on
、$off
和$once
这几个事件 API ,应用实例不再实现事件触发接口。 - 根据官方文档在 迁移策略 - 事件 API 的推荐,我们可以用 mitt 或者 tiny-emitter 等第三方插件来实现
EventBus
。
6. $parent
或$root
与$children
- 适用场景:通过共同祖辈parent或者root搭建通信侨联
-
$children
已被废弃 -
$parent
使用方式与vue2.x有所区别($root
同理)
vue2.0 | vue3.0 |
---|---|
this.$parent.父组件的方法名/父组件的属性名 | import {getCurrentInstance} from 'vue'; const {proxy} = getCurrentInstance(); proxy.$parent.父组件的方法名/父组件的属性名 |
7. $attrs
与 $listeners
- 适用场景:祖先传递属性(可以是值,可以是函数)给子孙
- 使用方式与vue2.0一致
-
$attrs
:包含了父作用域中不被认为 (且不预期为) props 的特性绑定 (class 和 style 除外),并且可以通过v-bind=”$attrs”
传入内部组件。当一个组件没有声明任何 props 时,它包含所有父作用域的绑定 (class 和 style 除外)。 -
$listeners
:包含了父作用域中的 (不含 .native 修饰符) v-on 事件监听器。它可以通过v-on=”$listeners”
传入内部组件。它是一个对象,里面包含了作用在这个组件上的所有事件监听器,相当于子组件继承了父组件的事件。
<!-- Father.vue -->
<child msg="hello" attrsText="测试attrsText" @listenersFun="listenersFun"/>
export default defineComponent({
components: {
child
},
setup(props, context) {
const listenersFun = (msg: string) => {
console.log(msg);
}
return {
listenersFun
};
},
});
<!-- Child.vue -->
<grandsun v-bind="$attrs" v-on="$listeners"/>
export default defineComponent({
components: {
grandsun
},
// 子组件的props没有配置attrsText,故attrsText存在context.attrs中
props: {
msg: {
type: String,
default: () => ''
}
},
});
<!-- Grandsun.vue -->
export default defineComponent({
setup(props, context) {
console.log('通过attrs获取祖父的属性值:', context.attrs.attrsText)
context.emit('listenersFun', '通过listeners跨级调用祖父的函数');
},
});
8. provide
与 inject
- 适用场景:祖先传递值给子孙
- 使用方式与vue2.0略有差异,vue2.0的provide和inject都为配置项,而在 3.x , provide 需要导入并在 setup 里启用,并且现在是一个全新的方法。在 3.x , provide 需要导入并在 setup 里启用,并且现在是一个全新的方法。
// father.vue
import { provide } from 'vue';
// provide出去(key, value)
provide('msg', '祖父传递一个属性给子孙');
// Grandsun.vue
import { inject } from 'vue';
console.log('通过inject获取祖父的属性值:', inject('msg'))
参考:
https://segmentfault.com/a/1190000022708579
https://juejin.cn/post/6844903990052782094#heading-0
https://vue3.chengpeiquan.com/communication.html#%E7%88%B6%E5%AD%90%E7%BB%84%E4%BB%B6%E9%80%9A%E4%BF%A1
https://blog.csdn.net/qq_15601471/article/details/122032034
网友评论