vue 组件之间传递参数
1. props 父向子传递
1. 父组件发送数据
<!-- 父组件 -->
<template>
<div>
<child-component :message="parentMessage"></child-component>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
parentMessage: '这是来自父组件的消息'
};
}
};
</script>
2. 子组件用 props 属性接收数据
<!-- 子组件 -->
<template>
<div>{{ message }}</div>
</template>
<script>
export default {
props: ['message']
};
</script>
props 接收可以校验。类型校验、非空校验、默认值、自定义校验
props: {
校验的属性名: {
type: 类型, // String , Number, Boolean
required: true, // 是否必填
default: 默认值, //默认值
validator (value) {
// 自定义校验逻辑
return 是否通过校验
}
}
}
2. $emit 子向父传递
子组件可以通过emit方法触发一个自定义事件,并将数据传递给父组件。父组件可以通过在子组件标签上监听这个自定义事件来接收数据。
1. 子组件通过 $emit 定义触发事件,并传递数据
<!-- 子组件 -->
<template>
<button @click="sendMessage">发送消息给父组件</button>
</template>
<script>
export default {
methods: {
sendMessage() {
this.$emit('childMessage', '这是来自子组件的消息'); // $emit(事件名, 消息数据)
}
}
};
</script>
2. 父组件通过监听子组件事件名监听该事件,并定义方法接收数据
<!-- 父组件 -->
<template>
<div>
<!-- @子组件事件名="父组件定义的方法名" -->
<child-component @childMessage="handleChildMessage"></child-component>
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
methods: {
handleChildMessage(message) {
console.log(message);
}
}
};
</script>
3. Vuex
通过在Vuex中定义全局的状态,并在组件中使用getter和mutation来访问和修改状态,实现多组件之间数据通信。
1. 安装Vuex并创建store 仓库
现在默认是vue3版本的,如果是vue2 项目要指定下载3版本
npm i Vuex@3
2. 在store中定义状态、mutation、action等。
<!-- Vuex Store(store.js) -->
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
counter: 0
},
mutations: {
increment(state) {
state.counter++;
}
}
});
3. 在组件中通过this.$store访问store,并使用getter获取状态,使用mutation或action修改状态。
父组件
<!-- 父组件(Parent.vue) -->
<template>
<div>
<h1>父组件</h1>
<ChildComponent />
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex';
import ChildComponent from './ChildComponent.vue';
export default {
components: { ChildComponent },
computed: {
...mapState(['counter'])
},
methods: {
...mapMutations(['increment'])
}
};
</script>
子组件
<!-- 子组件(ChildComponent.vue) -->
<template>
<div>
<h2>子组件</h2>
<p>计数器:{{ counter }}</p>
<button @click="increment">增加</button>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex';
export default {
computed: {
...mapState(['counter'])
},
methods: {
...mapMutations(['increment'])
}
};
</script>
4. 插槽
通过插槽,父组件可以将自己的模板内容传递给子组件,并在子组件的指定位置渲染出来。
1. 在子组件中定义插槽
2. 父组件使用子组件时,通过插槽向子组件传递数据
方式一:默认插槽
默认插槽是最基本的插槽类型,用于在组件内传递和显示任意内容。如果没有给插槽命名,Vue会将内容传递到默认插槽中。
//子组件
<template>
<div class="my-component">
<slot></slot> <!-- 默认插槽 -->
</div>
</template>
//父组件
<template>
<MyComponent>
<p>This is some default slot content!</p>
</MyComponent>
</template>
方式二:具名插槽
具名插槽允许我们在组件中定义多个插槽,每个插槽都有一个唯一的名称。这样可以在组件中更精确地控制内容的显示位置。
子组件
//子组件(MyComponent.vue)
<template>
<div class="my-component">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot> <!-- 默认插槽 -->
</main>
<footer>
<slot name="footer"></slot><!--具名插槽-->
</footer>
</div>
</template>
父组件
//父组件
<template>
<MyComponent>
<template v-slot:header>
<h1>Header Content</h1>
</template>
<p>This is some default slot content!</p>
<template v-slot:footer>
<p>Footer Content</p>
</template>
</MyComponent>
</template>
在这个例子中,<h1>Header Content</h1>将会显示在<slot name="header"></slot>位置,<p>Footer Content</p>将会显示在<slot name="footer"></slot>位置,而默认插槽中的内容仍会显示在<slot></slot>位置。
方式三:作用域插槽
作用域插槽是一种特殊类型的插槽,允许我们在父组件中访问子组件的数据。这在需要动态渲染内容时特别有用。在插槽中,是使用插槽的地方传递数据到定义的插槽中,但在作用域插槽中,有种逆流而上的感觉,数据是定义的插槽返回给插槽的使用者的。
子组件存放一个带数据的插槽
<template>
<div class="child">
<h3>这里是子组件</h3>
<slot :data="nameList"></slot>
</div>
</template>
export default {
data: function(){
return {
nameList: ['张三','李四','王五','赵六']
}
}
}
父组件通过 “slot-scope” 来接收子组件传过来的插槽数据,再根据插槽数据来填充插槽的内容
<template>
<div class="father">
<h3>这里是父组件</h3>
<!--第一次使用:用flex展示数据: class="tmpl"-->
<child>
<template slot-scope="userInfo">
<div class="tmpl">
<span v-for="item in userInfo.nameList">{
{item}}</span>
</div>
</template>
</child>
<!--第二次使用:用列表展示数据-->
<child>
<template slot-scope="userInfo">
<ul>
<li v-for="item in userInfo.nameList">{
{item}}</li>
</ul>
</template>
</child>
<!--第三次使用:直接显示数据-->
<child>
<template slot-scope="userInfo">
{
{userInfo.nameList}}
</template>
</child>
<!--第四次使用:不使用其提供的数据, 作用域插槽退变成匿名插槽-->
<child>
我就是模板
</child>
</div>
</template>
总结
父组件
- 默认插槽的话直接在子组件的标签内写入内容即可
- 具名插槽是在默认插槽的基础上加上
slot
属性,值为子组件插槽name
属性值- 作用域插槽则是通过
slot-scope
获取子组件的信息,在内容中使用。这里可以用解构语法去直接获取想要的属性
子组件
- 插槽用
<slot>
标签来确定渲染的位置,里面放如果父组件没传内容时的后备内容- 具名插槽用
name
属性来表示插槽的名字,不传为默认插槽- 作用域插槽在作用域上绑定属性来将子组件的信息传给父组件使用,这些属性会被挂在父组件
slot-scope
接收的对象上
5. 事件总线
兄弟组件传递数据,事件总线提供了$emit
、$on
和$off
方法,分别用于触发事件、监听事件和移除事件监听。
- $emit:用于触发事件,并传递相关数据。
- $on:用于监听事件,并定义事件触发时的回调函数。
- $off:用于移除事件监听,防止内存泄漏。
使用步骤
1. 创建事件总线:单独的.js
文件中创建一个新的Vue实例,并将其导出为事件总线。或者,在Vue项目的入口文件(如main.js
)中,将事件总线挂载到Vue的原型上,使其成为全局可用的。
import Vue from 'vue';
export const EventBus = new Vue();
2. 引入事件总线:使用import
语句将事件总线引入到你需要使用它的组件中。
<template>
<button @click="sendMessage">Send Message to ComponentB</button>
</template>
<script>
import { EventBus } from '../event-bus.js';
export default {
methods: {
sendMessage() {
EventBus.$emit('message-from-a', 'Hello from Component A!');
}
}
};
</script>
3. 监听和触发事件: $emit() 触发并传送数据,$on() 监听并接收数据
4. 解绑事件:$off() 解绑,防止内存泄漏
<template>
<div>
<p>Message from ComponentA: {{ message }}</p>
</div>
</template>
<script>
import { EventBus } from '../event-bus.js';
export default {
data() {
return {
message: ''
};
},
created() {
EventBus.$on('message-from-a', (msg) => {
this.message = msg;
});
},
beforeDestroy() {
EventBus.$off('message-from-a');
}
};
</script>
6. provide 和 inject
适用于祖先向子孙传递数据。祖先组件通过 provide 提供数据,子孙组件用 jnject 接收数据。provide \ inject 主要解决了跨级组件之间通信问题。
祖先组件
// 祖先组件
export default {
provide: {
name:'张三'
}
}
子孙组件
// 子孙组件
export default {
inject: ['name'],
mounted () {
console.log(this.name); // 张三
}
}
值得一提的是,provide 和 inject 并不是响应式的。也就是说,如果祖先组件数据发生了变化,比如数据由张三变成里李四,那么在子孙组件中的数据不会改变,依然是张三。
如果将其变成响应式数据呢?需要使用 Vue.observable
祖先组件
<div>
A组件
<button @click="() => changeColor()">改变color</button>
<ChildrenB />
<ChildrenC />
</div>
// 使用 2.6 最新 API Vue.observable 优化响应式 provide
provide() {
this.theme = Vue.observable({
color: 'blue'
});
return {
theme: this.theme
}
},
methods: {
channgeColor(color) {
if(color) {
this.theme.color = color;
} else {
this.theme.color = this.theme.color === 'blur' ? 'red' : 'blur'
}
}
}
子孙组件
<template functional>
<div class="border2">
<h3 id="h3" :style="{ color: this.theme.color }">F 组件</h3>
</div>
</template>
<script>
export default {
inject: ["theme"]
}
7. attrs 和 listeners
$attrs
是一个对象,它包含了父作用域中没有被prop接收的所有属性(不包含class和style属性)。可以通过v-bind="$attrs"
直接将这些属性传入内部组件,实现父组件隔代向孙组件传值。
父组件将name
和age
属性传递给子组件,子组件通过v-bind="$attrs"
将这些属性(以及可能的其他未声明的属性)传递给孙组件。孙组件通过props
接收这些属性。
<!-- 父组件(Parent.vue) -->
<template>
<div>
<h1>父组件</h1>
<ChildComponent :name="parentName" :age="parentAge" />
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: { ChildComponent },
data() {
return {
parentName: 'Tom',
parentAge: 30
};
}
};
</script>
<!-- 子组件(ChildComponent.vue) -->
<template>
<div>
<h2>子组件</h2>
<GrandChildComponent v-bind="$attrs" />
</div>
</template>
<script>
import GrandChildComponent from './GrandChildComponent.vue';
export default {
components: { GrandChildComponent }
};
</script>
<!-- 孙组件(GrandChildComponent.vue) -->
<template>
<div>
<h3>孙组件</h3>
<p>父组件传递的名字:{{ name }}</p>
<p>父组件传递的年龄:{{ age }}</p>
</div>
</template>
<script>
export default {
props: ['name', 'age']
};
</script>
$listeners是一个对象,它包含了父组件中所有的v-on事件监听器(不包含.native修饰器的)。可以通过v-on="$listeners"将这些事件监听器传入内部组件,实现孙组件隔代向父组件传值。
如果孙组件需要向父组件发送事件,可以通过$emit触发事件,并在子组件中使用v-on="$listeners"将这些事件传递给父组件。然而,需要注意的是,$listeners通常用于孙组件向隔代的父组件发送事件,而不是直接用于父子组件间的通信。在实际应用中,父子组件间的通信更多地使用$emit和v-on。