组件的常用传值方式
- props
- vue自定义事件
- 全局事件总线
- v-model
- sync
- attrs与attrs与attrs与listeners
- $ref & $children & $parent
- provide与inject
- Vuex
- 插槽 ==> 作用域插槽
根据通信的2个组件间的关系来选择一种通信方式
父子
props
vue自定义事件
v-model
.sync
$ref, $children与$parent
插槽 ==> 作用域插槽
祖孙
$attrs与$listeners
provide与inject
兄弟或其它/任意
全局事件总线
Vuex
- 方式一: props
父组件向子组件传送数据
子组件接收到数据之后,不能直接修改父组件的数据。会报错,所以当父组件重新渲染时,数据会被覆盖。如果子组件内要修改的话推荐使用
computed
// Parent.vue 传送
<template>
<child :msg="msg"></child>
</template>
// Child.vue 接收
export default {
// 写法一 用数组接收
props:['msg'],
// 写法二 用对象接收,可以限定接收的数据类型、设置默认值、验证等
props:{
msg:{
type:String,
default:'这是默认数据'
}
},
mounted(){
console.log(this.msg)
},
}
- 方式二:.sync
可以帮我们实现父组件向子组件传递的数据 的双向绑定,所以子组件接收到数据后可以直接修改,并且会同时修改父组件的数据
// Parent.vue
<template>
<child :page.sync="page"></child>
</template>
<script>
export default {
data(){
return {
page:1
}
}
}
// Child.vue
export default {
props:["page"],
computed(){
// 当我们在子组件里修改 currentPage 时,父组件的 page 也会随之改变
currentPage {
get(){
return this.page
},
set(newVal){
this.$emit("update:page", newVal)
}
}
}
}
</script>
- 方式三 v-model
实现将父组件传给子组件的数据为双向绑定,子组件通过 $emit 修改父组件的数据
// Parent.vue
<template>
<child v-model="value"></child>
</template>
<script>
export default {
data(){
return {
value:1
}
}
}
// Child.vue
<template>
<input :value="value" @input="handlerChange">
</template>
export default {
props:["value"],
// 可以修改事件名,默认为 input
model:{
// prop:'value', // 上面传的是value这里可以不写,如果属性名不是value就要写
event:"updateValue"
},
methods:{
handlerChange(e){
this.$emit("input", e.target.value)
// 如果有上面的重命名就是这样
this.$emit("updateValue", e.target.value)
}
}
}
</script>
- 方式四 ref
注意:ref 如果在普通的DOM元素上,引用指向的就是该DOM元素;如果在子组件上,引用的指向就是子组件实例,然后父组件就可以通过 ref 主动获取子组件的属性或者调用子组件的方法
// Child.vue
export default {
data(){
return {
name:"天天"
}
},
methods:{
someMethod(msg){
console.log(msg)
}
}
}
// Parent.vue
<template>
<child ref="child"></child>
</template>
<script>
export default {
mounted(){
const child = this.$refs.child
console.log(child.name) // 天天
child.someMethod("调用了子组件的方法")
}
}
</script>
- 方式五 : $emit / v-on (子传父)
// Child.vue 派发
export default {
data(){
return { msg: "这是发给父组件的信息" }
},
methods: {
handleClick(){
this.$emit("sendMsg",this.msg)
}
},
}
// Parent.vue 响应
<template>
<child v-on:sendMsg="getChildMsg"></child>
// 或 简写
<child @sendMsg="getChildMsg"></child>
</template>
export default {
methods:{
getChildMsg(msg){
console.log(msg) // 这是父组件接收到的消息
}
}
}
- 方式六 EventBus
- EventBus 是中央事件总线,不管是父子组件,兄弟组件,跨层级组件等都可以使用它完成通信操作
// 方法一
// 抽离成一个单独的 js 文件 Bus.js ,然后在需要的地方引入
// Bus.js
import Vue from "vue"
export default new Vue()
// 方法二 直接挂载到全局
// main.js
import Vue from "vue"
Vue.prototype.$bus = new Vue()
// 方法三 注入到 Vue 根对象上
// main.js
import Vue from "vue"
new Vue({
el:"#app",
data:{
Bus: new Vue()
}
})
以方法一为例,使用如下
// 在需要向外部发送自定义事件的组件内
<template>
<button @click="handlerClick">按钮</button>
</template>
import Bus from "./Bus.js"
export default{
methods:{
handlerClick(){
// 自定义事件名 sendMsg
Bus.$emit("sendMsg", "这是要向外部发送的数据")
}
}
}
// 在需要接收外部事件的组件内
import Bus from "./Bus.js"
export default{
mounted(){
// 监听事件的触发
Bus.$on("sendMsg", data => {
console.log("这是接收到的数据:", data)
})
},
beforeDestroy(){
// 取消监听
Bus.$off("sendMsg")
}
}
- 方法七 Vuex
- Vuex 是状态管理器,集中式存储管理所有组件的状态。这一块内容过长,如果基础不熟的话可以看这个[Vuex](https://vuex.vuejs.org/zh/guide/),然后大致用法如下
store中的index.js
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import actions from './actions'
import mutations from './mutations'
import state from './state'
import user from './modules/user'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
user
},
getters,
actions,
mutations,
state
})
export default store
然后再main里面引入
import Vue from "vue"
import store from "./store"
new Vue({
el:"#app",
store,
render: h => h(App)
})
在组件中使用
import { mapGetters, mapMutations } from "vuex"
export default{
computed:{
// 方式一 然后通过 this.属性名就可以用了
...mapGetters(["引入getters.js里属性1","属性2"])
// 方式二
...mapGetters("user", ["user模块里的属性1","属性2"])
},
methods:{
// 方式一 然后通过 this.属性名就可以用了
...mapMutations(["引入mutations.js里的方法1","方法2"])
// 方式二
...mapMutations("user",["引入user模块里的方法1","方法2"])
}
}
// 或者也可以这样获取
this.$store.state.xxx
this.$store.state.user.xxx
- 方式八 插槽 slot
插槽就是子组件中的提供给父组件使用的一个占位符,用 表示,父组件可以在这个占位符中填充任何模板代码,如
HTML、组件等,填充的内容会替换子组件的标签。插槽显不显示、怎样显示是由父组件来控制的,而插槽在哪里显示就由子组件来进行控制
1. 默认插槽
//父组件
<template>
<son :title="电影">
<ul>
<li v-for="(item,index) in films" :key="index">{{ item }}</li>
</ul>
</son>
</template>
<script>
export default{
name:'fath',
components:{ son },
data:{
return {
films:['猫和老鼠','哆唻A梦','樱桃小丸子']
}
}
}
</script>
子组件
<template>
<div>
<h3>{{ title }}</h3>
<!-- 定义一个插槽(设置一个位置,等着组件的使用者进行填充) -->
<slot>这是一个插槽,当使用插槽时,此文字不展示被填充内容覆盖</slot>
</div>
</template>
<script>
export default{
name:'son',
props:['listData','title']
}
</script>
注意:
1 父级的填充内容如果指定到子组件的没有对应名字插槽,那么该内容不会被填充到默认插槽中。即具名插槽用name属性来表示插槽的名字,不传为默认插槽
2. 如果子组件没有默认插槽,而父级的填充内容指定到默认插槽中,那么该内容就不会填充到子组件的任何一个插槽中
3. 如果子组件有多个默认插槽,而父组件所有指定到默认插槽的填充内容,将会且全都填充到子组件的每个默认插槽中
2. 具名插槽
具名插槽其实就是给插槽娶个名字。一个子组件可以放多个插槽,而且可以放在不同的地方,而父组件填充内容时,可以根据这个名字把内容填充到对应插槽中
//父组件
<div>
<son>
<div v-slot:content>
<span>{{ 给内容区放点东西 }}</span>
</div>
<!-- vue 2.6版本后语法,在template中可以直接写v-slot:slot1 -->
<template slot="footer">
<div>
<span>{{ 给底部放点东西 }}</span>
</div>
</template>
</son>
</div>
子组件
<template>
<h2>{{ 这里是头部 }}</h2>
<slot name='content'>这里是内容插槽,当使用插槽时,此文字不展示被填充内容覆盖</div>
<slot name='footer'>这里是底部插槽,当使用者没有传递具体结构时,此文字会显示</div>
</template>
作用域插槽
作用域插槽其实就是带数据的插槽,即带参数的插槽,简单的来说就是子组件提供给父组件的参数,该参数仅限于插槽中使用,父组件可根据子组件传过来的插槽数据来进行不同的方式展现和填充插槽内容。
子组件
<template>
<div class="child">
<h3>这里是子组件</h3>
<slot :data="data"></slot>
</div>
</template>
export default {
data: function(){
return {
data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
}
}
}
父子间
<template>
<div class="father">
<h3>这里是父组件</h3>
<!--第一次使用:用flex展示数据: class="tmpl"-->
<child>
<template slot-scope="user">
<div class="tmpl">
<span v-for="item in user.data">{{item}}</span>
</div>
</template>
</child>
<!--第二次使用:用列表展示数据-->
<child>
<template slot-scope="user">
<ul>
<li v-for="item in user.data">{{item}}</li>
</ul>
</template>
</child>
<!--第三次使用:直接显示数据-->
<child>
<template slot-scope="user">
{{user.data}}
</template>
</child>
<!--第四次使用:不使用其提供的数据, 作用域插槽退变成匿名插槽-->
<child>
我就是模板
</child>
</div>
</template>
所以slot的用法可以分为三类,分别是默认插槽、具名插槽、作用域插槽
子组件中:
插槽用 标签来确定渲染的位置,里面放如果父组件没传内容时的后备内容
具名插槽用name属性来表示插槽的名字,不传为默认插槽
作用域插槽在作用域上绑定属性来将子组件的信息传给父组件使用,这些属性会被挂在父组件slot-scope接收的对象上
//Child.vue
<template>
<div>
<main>
//默认插槽
<slot>
//slot内为后备内容
<h3>没传内容</h3>
</slot>
</main>
//具名插槽
<header>
<slot name="header">
<h3>没传header插槽</h3>
</slot>
</header>
//作用域插槽
<footer>
<slot name="footer" testProps="子组件的值">
<h3>没传footer插槽</h3>
</slot>
</footer>
</div>
</template>
<style scoped>
div{
border:1px solid #000;
}
</style>
父组件
- 默认插槽的话直接在子组件的标签内写入内容即可
- 具名插槽是在默认插槽的基础上加上slot属性,值为子组件插槽name属性值
- 作用域插槽则是通过slot-scope获取子组件的信息,在内容中使用。这里可以用解构语法去直接获取想要的属性
// Parent.vue
<child>
<!-- 默认插槽 -->
<div>默认插槽</div>
<!-- 具名插槽 -->
<div slot="header">具名插槽header</div>
<!-- 作用域插槽 -->
<div slot="footer" slot-scope="slotProps">
{{slotProps.testProps}}
</div>
</child>