VUE组件通信的十种姿势

1,755 阅读7分钟

父子组件通信

1、父子组件通过prop传递数据

父组件可以将一条数据传递给子组件,这条数据可以是动态的,父组件的数据更改的时候,子组件接收的也会变化。

子组件被动的接收父组件的数据,子组件不要再更改这条数据了。

组件实例的作用域是孤立的,父组件不能直接使用子组件的数据,子组件也不能直接使用父组件的数据。

父组件在调用子组件的时候给子组件传递数据:

<template id="father">
        <div class="father">
            <p>我是父组件,这是我的fMsg:{{fMsg}}</p>
            <input type = "text" v-model = "fMsg">
            <hr>
            <son msg = "你好"></son>
        </div>
    </template>

父组件给子组件传递数据的时候,子组件需要利用props的属性来确定自己的预期数据,如果儿子没有通过props属性接受传递过来的数据,则数据会以自定义属性的方式,放在儿子最外层的根元素上面。

image

子组件通过props来接受父组件传递过来的数据,并且通过{{msg}}使用

components:{
            son:{
                template:"<div>我是son子组件!这是父组件传递给我的msg:{{msg}}</div>",
                //接收父组件传递来的属性  msg
                props:["msg"]
            }
        }

image

2、父组件通过v-bind指令传递自身变量给子组件

我们可以用 v-bind 来动态地将 prop 绑定到父组件的数据。每当父组件的数据变化时,该变化也会传导给子组件。

<template id="father">
        <div class="father">
            <p>我是父组件,这是我的fMsg:{{fMsg}}</p>
            <input type = "text" v-model = "fMsg">
            <hr>
            <!-- <son msg = "你好"></son> -->
            <son :msg = "fMsg"></son>
        </div>
    </template>

如果如果父组件传递属性给子组件的时候键名有'-'

<son :f-msg = "fMsg"></son>

子组件接收、使用的时候写成小驼峰的模式

components:{
            son:{
                template:"<div>我是son子组件!这是父组件传递给我的msg:{{fMsg}}</div>",
                //接收父组件传递来的属性  msg
                props:["fMsg"]
            }
        }

3、prop验证传递过来的数据

我们可以为组件的 prop 指定验证规则。如果传入的数据不符合要求,Vue 会发出警告。这对于开发给他人使用的组件非常有用

验证主要分为:类型验证、必传验证、默认值设置、自定义验证

类型验证

// 类型验证
// num:Number //父组件传递过来的num必须是Number类型

// 添加多个类型
num: [Number,String],

规定传递过来的数值类型必须是number,否则系统会报错

image

必传验证

规定父组件必须给子组件传递该值

//必传验证
// num:{
//     required: true
// }

默认值设置

当父组件不给子组件传递该值的时候,给子组件设定一个默认值

// 默认值设置
// num:{
//     default:100
// }

自定义验证

//自定义验证
                            num:{
                                validator(val){
                                    return val > 100
                                }
                            }

规定传递过来的数值必须要大于100,否则系统会报错

image

4、父子组件依靠应用类型的地址传递共享数据

单向数据流

Prop 是单向绑定的:当父组件的属性变化时,将传递给子组件,但是反过来不会。这是为了防止子组件无意间修改了父组件的状态,来避免应用的数据流变得难以理解。

<template id="father">
        <div class="father">
            <input type = "text" v-model = "message">
            <hr>
            <son :message = "message"></son>
        </div>
    </template>

    <template id = "son">
        <div>
            <p>这是子组件</p>
            <input type = "text" v-model = "message"></input>
        </div>
    </template>

另外,每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着你不应该在子组件内部改变 prop。如果你这么做了,Vue 会在控制台给出警告。

image

所以如果我们想实现父子间的数据共享,依靠的就是应用类型的地址传递,应将message写成对象的形式,传递的时候将对象传递给子组件,子组件引用的时候使用对象的value值。

<template id="father">
        <div class="father">
            <input type = "text" v-model = "message.value">
            <hr>
            <!-- 传递的时候将对象传递给子组件 -->
            <son :message = "message"></son>
        </div>
    </template>

    <template id = "son">
        <div>
            <p>这是子组件</p>
            <!-- 引用的时候使用对象的value值 -->
            <input type = "text" v-model = "message.value"></input>
        </div>
    </template>

这时候更改父组件的value值,子组件的数据同步更改,子组件修改value值的时候也同步修改了父组件的数据。这是因为不管是子组件还是父组件,我们操作的都是同一个对象,父组件直接把引用类型的地址传递给子组件,子组件没有直接修改对象,只是更改了里面的属性值。

父组件如果将一个引用类型的动态数据传递给子组件的时候,数据会变成双向控制的,子组件改数据的时候父组件也能接收到数据变化,因为子组件改的时候不是在改数据(地址),而是在改数据里的内容,也就是说引用类型数据的地址始终没有变化,不算改父组件数据。

注意:在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。 message:{val:""}

父子间数据共享(双向控制),基本不会使用,违背了单向数据流(父=》子)

5、viewmodel关系链

在组件间可以用过ref形成ref链,组件还拥有一个关系链($parent),通过这两种链;理论来说,任意的两个组件都可以互相访问,互相进行通信。

$parent:父组件

$children:子组件

$root:根组件

image

当子组件在set方法中修改父组件传递过来的值时,系统会报错,因为子组件不能修改父组件的数据。

Vue.component("bbb",{
        template:"#bbb",
        props:["msg"],
        computed:{
            /* ownMessage(){
                return this.msg;
            } */
            ownMessage:{
                get(){
                    return this.msg;
                },
                set(val){
                    this.msg = val //系统报错:子组件不能更改父组件传递的数据
                }
            }
        }
    })

image

所以这时候要使用$parent,让父组件自己更改自己的数据

set(val){
                    // this.msg = val //系统报错:子组件不能更改父组件传递的数据
                    // console.log(this)
                    // 相当于父组件自己更改了msg数据
                    this.$parent.msg = val;
                }

6、父组件通过ref标记获取子组件的数据

父组件在调用子组件的时候使用ref做标记

<template id="aaa">
        <div>
            <button @click = "get">点击获取bbb数据</button>
            <!-- 组件间不仅可以用过$root/$parent/$children来获取对应关系的组件,父组件还可以主动的通过ref为子组件做标记 -->
            <bbb ref = "b"></bbb>
        </div>
    </template>

父组件的this属性上有$refs标记,通过refs标记拿到子组件

image

// 通过ref标记更改子组件的数据
// this.$refs.b.message = "哈哈"

组件间不仅可以用过$parent/children/root来获取对应关系的组件,父组件还可以主动的通过ref为子组件做标记 也可以给dom做标记,也会形成ref链,也可以交互.

<button ref="btn" @click="get">get</button>
<bbb ref="b></bbb>  

image

注意多个子组件标记的是同一个键名,获取到的应该是一个数组

<bbb ref = "b" v-for = "(item,index) in 3" :key = "index"></bbb>

image

// 通过下标修改对应的数值
this.$refs.b[0].message = "哈哈"

运行效果:

image

子父组件通信

1、子组件通过父组件传递的方法来更改父组件的数据

父组件可以将更改自身数据的方法传递给子组件,子组件调用这个方法的时候,就可以给父组件传递数据,父组件被动的接收子组件的数据。

子组件声明一条自身的msg

Vue.component("son",{
        template:"#son",
        // 子组件接收父组件传递过来的方法
        props:["change"],
        data(){
            return{
                msg:"我是子组件"
            }
        }
    })

父组件先声明一条自己的数据

data(){
            return{
                // 父组件先声明一条自己的数据
                parentMsg:""
            }
        }

再写一个可以更改自身数据的方法

methods:{
            // 写一个可以更改自身数据的方法
            change(msg){
                this.parentMsg = msg
            }
        }

将写好的change方法传递给子组件

<template id="father">
        <div>
            <p>这是父组件</p>
            <p>子组件传递过来的值是:{{parentMsg}}</p>
            <hr>
            <!-- 调用子组件的时候,将更改自身数据的方法传递给子组件 -->
            <son :change = "change"></son>
        </div>
    </template>

子组件通过props接收父组件传递过来的change方法

props:["change"]

给p标签添加点击事件,点击即触发change方法,同时将自身的msg传递给父组件,相当于父组件的change方法被执行。

<template id="son">
        <div>
            <p>子组件说:{{msg}}</p>
            <p @click = "change(msg)">点击我触发父亲的change方法</p> 
        </div>
    </template>

父组件可以在页面中渲染子组件传递过来的数据

<p>子组件传递过来的值是:{{parentMsg}}</p>

运行效果:

image

2、通过自定义事件实现子父通信

每一个组件或者实例都会有自定义事件,和触发事件的能力,父组件给子组件绑定一个自定义事件,这个事件的处理程序却是父组件的一个方法,当子组件触发这个事件的时候,相当于父组件的方法被执行。

父组件想获取子组件的数据时,在调用子组件的时候给子组件绑定一个自定义事件change-event

<template id="father">
        <div>
            <p>这是父组件</p>
            <p>子组件传递过来的值是:{{parentMsg}}</p>
            <hr>
            <!-- 给子组件绑定一个自定义事件 -->
            <son @change-event = "change"></son>
        </div>
    </template>

在子组件中定义一个点击事件,点击p标签执行changeWord方法

<p @click = "changeWord">点击我触发父亲的change方法</p> 

在方法中编写changeWord方法,通过this.$emit来触发绑定在自己身上的自定义事件,第一个参数为事件名称change-event,第二个参数为触发这个函数的时候给他传递的数值:自身的msg。

methods:{
            changeWord(){
                //触发自身绑定的change事件
                this.$emit("change-event",this.msg)//第一个参数为触发事件的名字,第二个参数为触发这个函数的时候给他传递的数值
            }
        }

一旦触发绑定在自身上的自定义事件,相当于父组件的change方法被执行。

兄弟组件通信

1、通过viewmodel关系链

定义哥哥组件,给哥哥组件添加一个点击事件,点击触发hitLittle方法

<template id = "big-brother">
        <div>
            <p>我是哥哥</p>
            <button @click = "hitLittle">打弟弟</button>
        </div>
    </template>

定义弟弟组件,给弟弟组件添加一个p标签,由crying数据控制其显示与隐藏

<template id="little-brother">
        <div>
            <p>我是弟弟</p>
            <p v-if = "crying">呜呜呜</p>
        </div>
    </template>

在弟弟组件的data中声明crying数据,默认为false

Vue.component("little-brother",{
        template:"#little-brother",
        data(){
            return{
                crying:false
            }
        }
    })

在哥哥组件的methods中定义hitLittle方法,通过viewmodel关系链更改弟弟组件中的crying方法

 Vue.component("big-brother",{
        template:"#big-brother",
        methods:{
            hitLittle(){
                //在兄弟组件之间的通信,可以采用关系链和ref链去使用,解决兄弟之间通信问题。
                this.$parent.$children[1].crying = true;//让littel改变自身的crying状态
            }
        }
    })

运行效果:

image

2、viewmodel关系链+ref链

在弟弟组件中添加ref标记

<little-brother ref = "little"></little-brother>

在哥哥组件的hitLittle方法中通过viewmodel和ref链配合使用更改弟弟组件中的crying数据

hitLittle(){
                //在兄弟组件之间的通信,可以采用关系链和ref链去使用,解决兄弟之间通信问题。
                // this.$parent.$children[1].crying = true;//让littel改变自身的crying状态
                
                //viewmodel链和ref链配合使用
                this.$parent.$refs.little.crying = true;
            }

3、eventbus事件总线

创建一个空的实例

var angle = new Vue();

弟弟组件自己定义一个更改自身状态的方法

methods:{
            cry(){
                this.crying = true
            }
        }

在mounted生命周期函数中绑定一个自定义事件,第一个参数为自定义事件名,第二个函数为需要处理的函数

mounted(){
            // 绑定一个自定义事件,第一个参数为自定义事件名,第二个函数为需要处理的函数
            angle.$on("hit-little",this.cry)
        }

在哥哥组件中触发自定义事件

hitLittle(){
                //触发little-brother组件的hit-little事件
                angle.$emit("hit-little")
            }

4、vuex状态管理

vuex是vue提供的一个全局的状态管理工具,主要处理项目中多组件间状态共享。

Vuex是vue官方的一款状态管理工具,什么是状态呢?我们在前端开发中有一个概念:数据驱动,页面中任意的显示不同,都应该有一条数据来控制,而这条数据又叫做state,状态。

在vue中。组件间进行数据传递、通信很频繁,而父子组件和非父子组件的通信功能也比较完善,但是,唯一困难的就是多组件间的数据共享,这个问题由vuex来处理

(1)创建store

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

(2)设置state

state就是一个纯对象,上面有一些状态挂载

state: {
    num:0,
    name:"list"
  }

(3)在根实例里配置store

这样,我们就可以在任意的组件中通过this.$store来使用关于store的api

import store from './store/index'

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

(4)在Home组件中使用state

computed:{
    ...mapState({
      num:state=>state.num
    })
  },

(5)使用mutations更改state

mutations也是一个纯对象,里面包含很多更改state 的方法,这些方法的形参接收到state,在函数体里更改,这时,组件用到的数据也会更改,实现响应式。

mutations: {
    changeNum(state){
      state.num++
    }
  }

(6)在组件中调用mutations方法,更改组件中的state。

//使用vuex提供的mapMutations帮助我们在组件中调用mutations方法
...mapMutations(["changeNum"]),

//给按钮添加点击事件
<button @click = "changeNum">点击更改num值</button>

运行效果:

image