① 父给子传值 props;② $attrs 给子组件传值;③ $parent/$children;④ provide / inject;⑤ 子组件向父组件传递 emit;⑥ ref/$refs; ⑦ eventBus 事件总线。
父给子传值 props
我们通过在父组件引用子组件的时候,可以通过自定义属性进行值的传递,在子组件通过 props 进行接收。
/**
* 选项式 API写法:
*/
// parent.vue:
<template>
<div class="wrapper">
<Child :toChild="testData"></Child>
</div>
</template>
<script setup>
import { ref } from '@vue/reactivity';
import Child from './child.vue';
const testData = ref('传递给子组件的值');
</script>
// child.vue:
<template>
<div class="child_wrapper">
<p>{{ toChild }}</p>
</div>
</template>
<script>
export default {
// 写法一:
props: ['toChild'],
// 写法二:
props: ['toChild'], // 需要声明一下接受到了,否则会报警告
setup(props) { // 此处 props 不能省略
console.log(props); // Proxy {toChild: '传递给子组件的值'}
},
};
</script>
// parent 组件渲染为:
<div class="wrapper">
<div class="child_wrapper">
<p>传递给子组件的值</p>
</div>
</div>
/**
* setup 语法糖写法:
*/
// parent.vue:
<template>
<div class="wrapper">
<Child :toChild="testData"></Child>
</div>
</template>
<script setup>
import { ref } from '@vue/reactivity';
import Child from './child.vue';
const testData = ref('传递给子组件的值');
</script>
// child.vue:
<template>
<div class="child_wrapper">
<p>{{ propsData }}</p>
</div>
</template>
<script setup>
// 写法一:
const propsData = defineProps(['toChild']);
// 写法二:
const propsData = defineProps({
toChild: String,
});
// 写法三:
const propsData = defineProps({
toChild: {
type: String,
default: '默认值',
require: false,
},
});
</script>
// parent 组件渲染为:
<div class="wrapper">
<div class="child_wrapper">
<p>{"toChild": "传递给子组件的值"}</p>
</div>
</div>
注意:① 在启用 eslint 的情况下,调用 defineProps(),会出现 'defineProps' is not defined 报错,需要修改 .eslintrc.js 的配置;② defineProps()返回值为 Proxy 对象。
// .eslintrc.js:
module.exports = {
...
env: {
node: true,
// 需要添加这个配置
'vue/setup-compiler-macros': true,
},
...
}
$attrs 给子组件传值
① 我们的 $attrs 只会包括未被 props 继承的属性;② 当引用的子组件返回单个根节点时,非 prop 的 attribute 将自动添加到根节点的 attribute 中;③ 如果你不希望组件的根元素继承 attribute,可以在组件的选项中设置 inheritAttrs: false;④ 多个根节点上的 attribute 继承:子组件如果有多个根节点,那么这些根节点不具有自动绑定 attribute 的行为,需要通过 v-bind="$attrs" 在根节点上绑定,没有一个都没有绑定就会报警告多节点不能自动绑定 attribute ,需要手动指定;⑤ 非继承的属性,如果属性名有大写字母,会被转为小写。
// parent.vue:
<template>
<div class="wrapper">
<Child :selfData="testData" data-status="activated" @click="handle"></Child>
</div>
</template>
<script setup>
import { ref } from '@vue/reactivity';
import Child from './child.vue';
const testData = ref('传递给子组件的值');
const handle = () => {};
</script>
// child.vue:
<template>
<div class="child_wrapper"></div>
</template>
<script>
export default {
created() {
console.log(this.$attrs); // Proxy {selfData: '传递给子组件的值', data-status:
}, // 'activated', __vInternal: 1, onClick: ƒ}
};
</script>
// parent 组件渲染为:
<div class="wrapper">
<div class="child_wrapper" selfdata="传递给子组件的值" data-status="activated"></div>
</div>
// parent.vue:
<template>
<div class="wrapper">
<Child :toChild="testData" data-status="activated" @click="handle"></Child>
</div>
</template>
<script setup>
import { ref } from '@vue/reactivity';
import Child from './child-view.vue';
const testData = ref('传递给子组件的值');
const handle = () => {};
</script>
// child.vue:
<template>
<div class="child_wrapper">
<p>{{ toChild }}</p>
</div>
</template>
<script>
export default {
props: ['toChild'],
created() {
console.log(this.$attrs); // Proxy {data-status: 'activated', __vInternal: 1,
}, // onClick: ƒ}
};
</script>
// parent 组件渲染为:
<div class="wrapper">
<!--toChild 属性被子组件继承,故不在子组件根节点上-->
<div class="child_wrapper" data-status="activated">
<p>传递给子组件的值</p>
</div>
</div>
// parent.vue:
<template>
<div class="wrapper">
<Child :toChild="testData" data-Define="define" data-status="activated"
@click="handle"></Child>
</div>
</template>
<script setup>
import { ref } from '@vue/reactivity';
import Child from './child-view.vue';
const testData = ref('传递给子组件的值');
const handle = () => {};
</script>
// child.vue:
<template>
<div class="child_wrapper">
<p>{{ toChild }}</p>
</div>
</template>
<script>
export default {
inheritAttrs: false, // 禁用根元素继承 attribute
props: ['toChild'],
created() {
console.log(this.$attrs); // Proxy {data-Define: 'define', data-status:
}, // 'activated', __vInternal: 1, onClick: ƒ}
};
</script>
// parent 组件渲染为:
<div class="wrapper">
<div class="child_wrapper">
<p>传递给子组件的值</p>
</div>
</div>
// parent.vue:
<template>
<div class="wrapper">
<Child :toChild="testData" data-Define="define" data-status="activated"
@click="handle"></Child>
</div>
</template>
<script setup>
import { ref } from '@vue/reactivity';
import Child from './child.vue';
const testData = ref('传递给子组件的值');
const handle = () => {};
</script>
// child.vue:
<template>
<div class="child_wrapper">
<p>{{ toChild }}</p>
</div>
<!-- 如果 child_2, child_3 都没有 v-bind="$attrs", 就会报警告-->
<div class="child_2" v-bind="$attrs"></div>
<div class="child_3" v-bind="$attrs"></div>
</template>
<script>
export default {
props: ['toChild'],
created() {
console.log(this.$attrs); // Proxy {data-Define: 'define', data-status:
}, // 'activated', __vInternal: 1, onClick: ƒ}
};
</script>
// parent 组件渲染为:
<div class="wrapper">
<div class="child_wrapper"> <!--toChild 属性被子组件继承,故不在节点上-->
<p>传递给子组件的值</p>
</div>
<div class="child_2" data-define="define" data-status="activated"></div>
<div class="child_3" data-define="define" data-status="activated"></div>
</div>
$parent/$children
需要注意的是:① 这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据;② vue3 中已经移除 $children,故 vue3 不支持 $children。
通过 $parent 获取父级(父实例)的时候,可以获取父级的全部属性。
// parent.vue:
<template>
<Child></Child>
</template>
<script>
import Child from './child.vue';
export default {
components: {
Child,
},
setup() {
const b = 30;
const c = () => {};
return { b, c };
},
data() {
return {
a: 20,
};
},
methods: {
say() {},
},
computed: {
getInfor() {
return '获取信息';
},
},
};
</script>
// child.vue:
<template>
<div></div>
</template>
<script>
export default {
created() {
console.log(this.$parent.a); // 20
console.log(this.$parent); // Proxy {a: 20, b: 30, c: ()=>{}, getInfor:
}, // "获取信息", say: f()}
};
</script>
通过 $children 获取子级(子实例)的时候,可以获取子级的全部属性。
// parent.vue:
<template>
<div class="wrapper">
<Child></Child>
</div>
</template>
<script>
import Child from './child.vue';
export default {
components: {
Child
},
data() {
return {
obj: {}
};
},
mounted() {
console.log(this.$children); // 子组件实例列表(即组件实例的集合)
console.log(this.$children[0].a); // 10
console.log(this.$children[0].say); // ƒ say() {}
}
};
</script>
// child.vue:
<template>
<div class="child_wrapper"></div>
</template>
<script>
export default {
data() {
return {
a: 10
};
},
methods: {
say() {}
}
};
</script>
provide / inject
在 vue2 中
provide/inject 这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。一言而蔽之:祖先组件中通过 provide 来提供变量,然后在子孙组件中通过 inject 来注入变量。 provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
以下写法 vue3 也支持。
// parent.vue:
<template>
<Child></Child>
</template>
<script>
import Child from './child.vue';
export default {
components: {
Child,
},
provide: {
name: '张三',
},
};
</script>
// child.vue:
<template>
<button>子组件发送数据</button>
</template>
<script>
export default {
inject: ['name'],
created() {
console.log(this.name); // 张三
},
};
</script>
按照上边的例子,发现如果修改了 Provide 的 name 值,Child 的 name 并没有随着更改。官网也说了 Provide 并不是响应的。为了解决这个问题 需要把 name 变成 vue 的监听的对象,换句话说就是需要把 name 变成对象。
// parent.vue:
<template>
<Child></Child>
<button @click="handleData">修改数据</button>
</template>
<script>
import Child from './child-view.vue';
export default {
components: {
Child,
},
data() {
return {
obj: {
name: '张三',
},
};
},
provide() {
return {
obj: this.obj,
};
},
methods: {
handleData() {
this.obj.name = '修改了张三';
},
},
};
</script>
// child.vue:
<template>
<p>{{ obj }}</p>
</template>
<script>
export default {
inject: ['obj'],
created() {
console.log(this.obj);
},
};
</script>
在 vue3 中
在vue2中我们已经使用过provide和inject来实现祖孙组件之间的数据传递,但是在vue3中由于我们使用setup,此时我们应该如何去使用provide和inject函数呢?
在vue中帮我们提供了provide和inject的函数,我们可以直接在setup函数中使用即可。
注意:provide()提供的数据,子组件通过 inject()注入后,子组件可以直接修改父组件的值。但是在一些情况下,我们是不允许的,此时我们可以采用readonly来进行设置。
/**
* setup 语法糖写法:
*/
// parent.vue:
<template>
<Child></Child>
</template>
<script setup>
import { reactive, ref } from '@vue/reactivity';
import { provide } from '@vue/runtime-core';
import Child from './child.vue';
const a = { x: 10, y: 20 };
const b = 'code';
provide('A1', a);
provide('A2', reactive(a));
provide('B', ref(b));
</script>
// child.vue:
<template>
<div></div>
</template>
<script setup>
const { inject } = require('@vue/runtime-core');
const A1 = inject('A1');
const A2 = inject('A2');
const B = inject('B');
console.log(A1, A2, B); // {x: 10, y: 20}
</script> // Proxy {x: 10, y: 20}
// RefImpl {value: "code"}
/**
* 选项式 API 写法:
*/
// parent.vue:
<template>
<Child></Child>
</template>
<script>
import { reactive, ref } from '@vue/reactivity';
import { provide } from '@vue/runtime-core';
import Child from './child-view.vue';
export default {
components: {
Child,
},
setup() {
const a = { x: 10, y: 20 };
const b = 'code';
provide('A1', a);
provide('A2', reactive(a));
provide('B', ref(b));
},
};
</script>
// child.vue:
<template>
<div></div>
</template>
<script>
const { inject } = require('@vue/runtime-core');
export default {
setup() {
const A1 = inject('A1');
const A2 = inject('A2');
const B = inject('B');
console.log(A1, A2, B); // {x: 10, y: 20}
}, // Proxy {x: 10, y: 20}
}; // RefImpl {value: "code"}
</script>
/**
* 设置 provide() 提供的值只读,子组件无法修改:
*/
// parent.vue:
<template>
<Child></Child>
</template>
<script>
import { reactive, ref, readonly } from '@vue/reactivity';
import { provide } from '@vue/runtime-core';
import Child from './child-view.vue';
export default {
components: {
Child,
},
setup() {
const a = { x: 10, y: 20 };
const b = 'code';
provide('A1', a);
provide('A2', readonly(reactive(a))); // reactive(a) 的值只读,子组件无法修改
provide('B', ref(b));
},
};
</script>
// child.vue:
<template>
<div>{{ B }}</div>
<button @click="handleData">修改父组件传递的数据</button>
</template>
<script>
const { inject } = require('@vue/runtime-core');
export default {
setup() {
const A1 = inject('A1');
const A2 = inject('A2');
const B = inject('B');
console.log(A1, A2, B);
const handleData = () => {
B.value = '修改了 code';
};
return { B, handleData };
},
};
</script>
/**
* 父组件中定义修改父组件数据的方法,并提供给子组件调用:
*/
// parent.vue:
<template>
<Child></Child>
</template>
<script>
import { reactive, ref, readonly } from '@vue/reactivity';
import { provide } from '@vue/runtime-core';
import Child from './child-view.vue';
export default {
components: {
Child,
},
setup() {
const a = { x: 10, y: 20 };
let b = ref('code');
const changeCode = () => {
b.value = '修改了 code';
};
provide('A1', a);
provide('A2', readonly(reactive(a))); // reactive(a) 的值只读,子组件无法修改
provide('B', b);
provide('changeCode', changeCode);
},
};
</script>
// child.vue:
<template>
<div>{{ B }}</div>
<button @click="handleData">直接修改父组件传递的数据</button>
<button @click="changeCode">调用父组件提供的方法修改父组件的数据</button>
</template>
<script>
const { inject } = require('@vue/runtime-core');
export default {
setup() {
const A1 = inject('A1');
const A2 = inject('A2');
const B = inject('B');
console.log(A1, A2, B);
const handleData = () => {
// 不建议子组件直接修改父组件中的值
B.value = '修改了 code';
};
const changeCode = inject('changeCode'); // 注入父组件提供修改父组件数据的方法
return { B, handleData, changeCode };
},
};
</script>
子组件向父组件传递 emit
/**
* 但是这种方法在 Vue3.x 中不推荐使用。
*/
// parent.vue:
<template>
<Child @testEmit="testEmit"></Child>
</template>
<script>
import Child from './child-view.vue';
export default {
components: {
Child,
},
methods: {
testEmit(data) {
console.log('子组件传递的数据:', data); // 子组件传递的数据: child data
},
},
};
</script>
// child.vue:
<template>
<button @click="sendData">子组件发送数据</button>
</template>
<script>
export default {
methods: {
sendData() {
this.$emit('testEmit', 'child data');
},
},
};
</script>
vue3 中推荐写法:通过 defineEmits()定义事件,并返回一个函数。
// parent.vue:
<template>
<Child @testEmit="testEmit"></Child>
</template>
<script>
import Child from './child-view.vue';
export default {
components: {
Child,
},
methods: {
testEmit(data) {
console.log('子组件传递的数据:', data); // 子组件传递的数据: child data
},
},
};
</script>
// child.vue:
<template>
<button @click="sendData">子组件发送数据</button>
</template>
<script setup>
const { defineEmits } = require('@vue/runtime-core');
const emit = defineEmits(['testEmit']);
const sendData = () => {
emit('testEmit', 'child data');
};
</script>
ref/$refs
作用:获取节点或组件实例。
场景:简单的获取节点或组件实例的属性或者方法,但并不改变其数据。
缺陷:必须在模板渲染之后,不是响应式的,时不时配合$nextTick。
ref 放在不同的位置,有不同的效果:
- 元素节点时,可以通过
this.$refs.元素节点 ref 值得到节点的属性或者方法,如<p ref="p">hello</p> - 组件时,可以通过
this.$refs.组件 ref 值得到相应的组件实例,从而得到组件上面的属性和方法,如<child-component ref="child"></child-component> - v-for语法时,可以通过
this.$refs.items得到节点或组件实例的数组,具体的某项,需要this.$refs.items[index],如<item ref="items" v-for=".">hello</item>
ref 要在组件渲染完成之后才能生效
ref 是以属性的方式存在标签上,所以在组件渲染完成之后才会生效。 因此$refs 不是响应式的,避免在模板或计算属性中访问 $refs。
// ref 在元素节点上:
<template>
<div class="child_wrapper">
<div ref="divDom">
<p>子节点</p>
</div>
</div>
</template>
<script>
export default {
data() {
return { };
},
mounted() {
console.log(this.$refs.divDom); // <div><p>子节点</p></div>
}
};
</script>
// ref 在组件上:
// parent.vue:
<template>
<div class="wrapper">
<Child ref="childDom" data-active="active"></Child>
</div>
</template>
<script>
import Child from './child.vue';
export default {
components: {
Child
},
data() {
return {
obj: {}
};
},
mounted() {
console.log(this.$refs.childDom); // 获取到子组件实例
console.log(this.$refs.childDom.a); // 10
console.log(this.$refs.childDom.init); // ƒ init() {}
console.log(this.$refs.childDom.$attrs); // {data-active: 'active'} (获取到子组件
} // 根节点的属性)
};
</script>
// child.vue:
<template>
<div class="child_wrapper">
<div ref="childDom">
<p>子节点</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
a: 10
};
},
methods: {
init() {}
},
mounted() {
console.log(this.$attrs); // {data-active: 'active'}
}
};
</script>
created的时候,$refs只是一个空对象。mounted的时候,$refs才有数据,但没有渲染的节点或者组件依旧获取不到- 将某个节点或者组件,
v-if的值从false=>true的瞬间,因为模板还未更新,所以依旧获取不到 - 常配合
nextTick
eventBus 事件总线
vue3 不支持 eventBus 了,因为原先实例上的三个方法 $on、$off 和 $once 被删除掉了。
$on 必须比 $emit 先加载,否则无法监听到传递的数据。
作用:实现任意组件间的通信
有如下两种实现方式
① 全局事件总线:
在 main.js 文件中定义
// src/main.js:
new Vue({
el: '#app',
router,
store,
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this;
}
})
// 或者:
Vue.prototype.$bus = new Vue();
new Vue({
el: '#app',
router,
store,
render: h => h(App),
})
使用方法
// 传值 (可以在你定义的方法中传值)
this.$bus.$emit('定义名称',值);
// 监听数据 (在mounted监听数据)
this.$bus.$on('定义名称' val=>{});
// 销毁(在beforeDestroy销毁)
this.$bus.$off('定义名称');
② 局部事件总线:
在main.js的同级建一个bus.js
// src/bus.js:
import Vue from "vue";
export default new Vue;
使用方法
// 1.引入(传值和监听数据部分都需引入bus.js文件)
import Bus from '@bus.js';
// 传值(我是在beforeDestroy中传值的)
Bus.$emit('定义名称',值);
// 监听数据 (我在beforeCreate 中监听数据)
Bus.$on('定义名称',val=>{});
//销毁(在监听数据页面的 beforeDestroy 中销毁)
Bus.$off('定义名称');