VUE2组件通信

390 阅读4分钟

1. props

  • 父子通信最常用的方式
  • 子组件接收到数据后,不能直接修改父组件的数据,会报错
  • 代码实现
//父组件
<child :msg="msg" />

//子组件
export default{
    //写法1:用数组接收
    props:['msg'];
    //写法2:用对象接收,可以限定接收的数据类型,默认值等
    props:{
        msg:{
            type:String,
            default:"这是默认值"
            }
          }
}

2. .sync

  • 对props进行双向绑定,即子组件可修改父组件传入的数据
  • 于2.0移除,2.3.0重新引入,作为编译的语法糖,被自定扩展为一个自动更新父组件属性的v-on监听器
//例如 <child :foo.sync='msg' />
//会被扩展为
//<child :foo='bar' @update:foo='val=>bar=val' />
//当子组件更新foo时,需要触发一个更新事件
this.$emit("update:foo",newValue)
  • 代码实现
//父组件
<child :msg.sync = 'msg' />

//子组件
<input type="text" v-model='data' @input='fn' />
export default{
    props:['msg'],
    data(){
        return{
            data:this.msg //定义一个变量接收props传下来的内容,后面说为什么
        }
    },
    methods:{
        fn(e){
            this.$emit("update:foo",e.target.value)
        }
    },
    watch:{
        msg(){
            this.data = this.msg
        }
    }
            
}
  • 问题说明
    1. 为什么要用变量接收props传递的数据?
    • 如果我们不用变量接收的话,直接使用是可以实现父子双向通信的,但问题是vue不建议我们直接修改props的值,所以会有警告⚠️。于是我们可以使用一个变量去接收props的值
    1. 基于问题1出现的另一个问题,修改父组件数据,子组件无变化
    • 原因就是我把子组件接收的数据用变量(data)接收导致的,data只会赋值一次,所以可以使用watch监听props中的数据,每次发生变化都将该值赋值给data

3. v-model

  • 和.sync类似,父组件通过v-model给子组件绑定一个value属性和input事件,子组件通过$emit修改副组件的数据
  • 代码实现
//父组件
<child v-model='msg' />
//扩展为
<child :value='msg' @input ='$event.target.value' />
//子组件
export default {
    props:['value'],
    methods:{
        handlerChange(e){
            this.$emit("input", e.target.value)
        }
    },
}

4. ref

  • 如果把ref绑定给普通dom元素上,引用指向的就是该dom元素
  • 如果绑定在子组件上,引用的指向就是子组件实例,然后父组件通过ref主动获取子组件的属性或调用子组件的方法
  • 代码实现
//子组件
data(){
    return{
        msg:'qwe' //定义数据
    }
},
methods:{
    // 定义方法
    fn(){
        alert(this.msg)
    }
}

//父组件
<child ref="child" />
<button @click='getCilck'>调用子组件函数</button>
export default{
    data(){
        return{
            msg:""
        }
    },
    mounted(){
        //组件挂载之后获取子组件数据
        this.getCilck()
    },
    methods:{
        getCilck(){
           const child = this.$refs.child
           this.msg = child.msg //获取子组件数据
           child.fn() // 调用子组件的方法
    }
}

5. $attrs/$listeners

  • 多层嵌套组件传递数据时,如果只是传递数据,不做中间处理就可以使用,比如爷孙组件通信
  • $attrs:包含父作用域除class和style的非props属性
  • $listeners:包含父作用域里.native外的监听事件集合
  • 使用方法:
    1. 爷——>孙传递数据,子组件不声明props接收,直接v-bind="$attrs",绑定给孙组件,孙组件声明props接收
    2. 爷——>孙传递方法,同样先传给子组件,子组件不声明props接收,通过v-on="$listeners"绑定给孙组件,孙组件声明props接收
  • 代码实现
//父组件
<child :name='name' :toSun="toSun" /> 
//子组件
<sun v-bind="$attrs" v-on="$listeners" /> 
//孙组件
export default{
    props:['name','toSun']
}

6. $children/$parent

  • $children:获取到一个包含所有子组件的组件对象数组,可以直接拿到子组件中的所有数据和方法
  • $parent:获取到一个父节点的组件对象,同样包含父节点中的所有数据和方法
  • 代码实现
//父组件获取子组件的数据和方法,n代表子组件下标
    this.$children[n].msg
    this.$children[n].fn()
//子组件获取父组件的数据和方法
    this.$parent.msg
    this.$parent.fn()

7. provide/inject

  • vue2.2.0新增的API
  • provide可以在祖先组件中指定想传给后代的数据和方法,然后在任何后代组件中都可以用inject来接收provide提供的数据和方法
  • vue不会对provide中的变量进行响应式处理。所以,要想接收的变量是响应式的,provide提供的变量本身就需要是响应式的
  • provide/inject实现全局状态管理
    • 在根组件的provide中传入变量,后代组件就可以通过inject接收
    • 相比vuex优点:简单暴力
    • 缺点:不可溯源
  • 官网实例
//父组件
var provide={
    provide:{
        foo:'bar'
    }
}
//子组件注入foo
var child = {
    inject:['foo'],
    mounted(){
        console.log(this.foo)
    }
}
  • 代码实现
//父组件
provide(){
    return{
        father:this, //子组件通过这个this可以获取父组件的所有数据和方法
        toChild:this.toChild, //methids中定义的函数
        name:this.name //data中定义的数据
    }
}
//子组件
inject:['father','toChild','name'],
mounted(){
    console.log(this.father.name) //通过父组件的this获取到name
    this.toChild() //调用父组件传递的方法
    console.log(this.name) //调用父组件传递的数据
}

8. EventBus事件总线

  • EventBus是中央事件总线,不管父子,兄弟,跨层级等都可以使用

  • 初始化(2种方式)

    1. 创建一个bus.js文件(局部引入即可)
        //bus.js
        import Vue from 'vue'
        export default const EventBus = new Vue()
    
    1. 在main.js中(此时创建的为全局事件总线)
        Vue.prototype.$EventBus = new Vue()
    
  • 概念图

    evnetbus.jpg

  • 代码实现

//功能:组件1点击"+"或"-"改变data中的point数据,组件2能随着改变而改变
//组件1 发布
data(){
    return{
        point:0
    }
},
methods:{
    add(){
        this.point++
    },
    cut(){
        this.point--
    },
    //发布point的方法
    fn(){
        this.$bus.$emit('sendMsg',{point:this.point}) //参数1:发布事件名,参数2:额外参数
    }
},
mounted(){
    //页面挂载后发布point
    this.fn()
},
watch:{
    // 监视point,每次改变都会重新发布
    point(){
        this.fn()
    }
}
//组件2 订阅
created(){
    this.$bus.$on('sendMsg',data=>this.point=data.point) //参数1:订阅事件名,参数2:事件回调函数
},
beforeDestory(){
    //实例销毁前记得解绑事件总线
    this.$bus.$off('sendMsg')
}
  • 注意:订阅事件总线需要在生命周期中订阅

9. VUEX

  • VUEX是状态管理器,集中式管理所有组件的状态
  • 核心概念
    1. store:用于管理state,getter,mutation,action等,并向外提供一些方法等对象
    2. state:用于存储数据(原生数据)
    3. getter:修饰器,用于修饰state数据,通过计算属性方法(走缓存),也可以通过方法访问(不走缓存)
    4. mutation:用于直接修改state的数据,一般写入同步代码
    5. action:用于间接修改state等数据,一般写入异步代码
  • 配置vuex
    1. 在src下创建store文件夹,然后创建index.js
    import Vue from 'vue';
    import Vuex from 'vuex';
    Vue.use(Vuex);
    const store = new Vuex.Store({
        state: {},
        getters:{},
        mutations:{},
        actions:{}
    });
    export default store;
    
    1. 修改main.js
    import Vue from 'vue'
    import App from './App.vue'
    import store from './store'
    Vue.config.productionTip = false
    new Vue({
      store,
      render: h => h(App),
    }).$mount('#app')
    
  • 代码实现
    1. 读取state中的某个属性
        //方式1
        computed:{
            getPoint(){
                return this.$store.point
            }
        }
        //方式2
        import {mapState} from 'vuex'
        export default{
            computed:{
                ...mapState(['point']
            }
        }
        
    
    1. 修饰state中的某个属性
        //store/index.js
        const store = new Vuex.Store({
            state: {
                name:'冬梅'
            },
            getters:{
                newName(state){
                    return `马${state.name}`
                }
            },
        });
        export default store;
        
        //读取getter
        //方式1
        computed:{
            getGetter(){
                return this.$store.getters.newName
            }
        }
        //方式2
        import {mapGetter} from 'vuex'
        export default{
            computed:{
                ...mapGetter(['newName'])
            }
        }
    
    1. 直接修改state中的某个属性
        //store/index.js
       import Vue from 'vue';
       import Vuex from 'vuex';
       Vue.use(Vuex);
       const store = new Vuex.Store({
           state: {
               point:20
           },
           mutations:{
               pointLose(state){
                   return state.point--
               }
           },
       });
       export default store;
       
       //修改属性
       //方式1
       lose(){
           this.$store.commit('pointLose')
       }
       //方式2
       import {mapMutations} from 'vuex'
       export default{
           methods:{
               ...mapMutations(['pointLise']),
               lose(){
                   this.pointLose()
               }
           },
       }
    
    1. 间接修改state中的某个属性
        //store/index.js
       import Vue from 'vue';
       import Vuex from 'vuex';
       Vue.use(Vuex);
       const store = new Vuex.Store({
           state: {
               point:20
           },
            mutations:{
               pointLose(state){
                   return state.point--
               }
           },
           action:{
               setPoint(content){
                   return new Promise(reslove=>{
                       setTimeout(()=>{
                           content.commit('pointLose')
                       },1000)
                   }
               }
           },
       });
       export default store;
       
       //修改属性
       //方式1
       async lose2(){
           this.$store.dispatch('setPoint')
       }
       //方式2
       import {mapActions} from 'vuex'
       export default{
           methods:{
               ...mapActions(['setPoint']),
               async lose2(){
                   await this.setPoint()
               }
           },
       }
    

10. slot插槽

  • 相当于占位符,它在组件中给html模版占了一个位置
  • 插槽又分匿名插槽,具名插槽,作用域插槽
  • 代码实现
        //父组件
        <child >
           <!-- 匿名插槽 -->
           <template v-slot:default>
                {{name}}
           </template>
           <!-- 具名插槽 -->
           <template v-slot:fatherA>
               fatherA
           </template>
           <template v-slot:fatherB>
               fatherB
           </template>
           <!-- 作用域插槽 -->
           <template v-slot:fatherC='point'>
               C: {{point}}
           </template>
        </child>
        //子组件
             <div>1.匿名插槽</div>
             <slot></slot>
             <div>2.具名插槽</div>
             <slot name="fatherA"></slot>
             <slot name="fatherB"></slot>
             <div>3.作用域插槽</div>
             <slot name="fatherC" :point='point'></slot>

11. 自定义事件

  • 组件的自定义事件实现的就是子组件向父组件通信的功能,所以,自定义事件的绑定动作需要在父组件中完成
  • 组件的自定义事件的触发动作需要在子组件中完成,给谁绑定的事件,就找谁触发
  • 代码实现
// 父组件
<template>
 <div>
       <div class="box">
       <div>这是父组件接收的数据:<input type="text" v-model="name"></div>
       <sun @sendMsg="getChild" ></sun>
       </div>
   </div>
</template>

<script>
import sun from "./sun.vue";
export default {
   data(){
       return{
           name:''
       }
   },
  
   methods:{
       getChild(msg){
           this.name = msg
       }
   },
   components:{
       sun
   }
}
</script>
// 子组件
<template>
 <div class="box2">
     <div>这是子组件中的数据:<input type="text" v-model="fdata" @input="change"></div>
 </div>
</template>

<script>
export default {
  data(){
    return{
      fdata:'$emit'
    }
  },
  mounted(){
    this.change()
  },
  methods:{
    change(){
     this.$emit('sendMsg',this.fdata)
    }
  }
}
</script>