Vue组件通信

55 阅读3分钟

Vue2组件通信

props

父传子

:boom:利用props属性,由父组件传递给子组件,子组件收到的数据不能修改,不然会报错


<template>
  <div>
    我是父组件
    <div>
      <child :data="paraentData"></child>
    </div>
  </div>
</template>
<script>
import child from '../views/Home.vue'
export default {
  name:'parent',
  data() {
    return {
      paraentData:'我来自父组件数据'
    }
  },
  components:{
    child,
  },

}
</script>
++++++++++++++++++++++++++++++
<template>
  <div>
    <div>子组件</div>
    {{data}}
  </div>
</template>

<script>
export default {
  name: 'child',
  props:["data"],
   mounted() {
    console.log(this.data)
 }, 
}
</script>

$emit

子传父

:boom:在父组件总个子组件身上绑定一个事件,子组件通过this.$emit('父组件给绑定的事件名',传给父组件的数据)调用父组件给绑定的事件将数据作为参数传递给父组件

<template>
  <div>
    我是父组件
    <div>
      <child @GetData="GetChildData"></child>
    </div>
  </div>
</template>
<script>
import child from "../views/Home.vue";
export default {
  name: "parent",
  components: {
    child,
  },
  methods: {
    GetChildData(data) {
      alert(data);
    },
  },
};
</script>
+++++++++++++++++++
<template>
  <div>
    <div>我是子组件</div>
    <button @click="SendData">向父组件传递数据</button>
  </div>
</template>

<script>
export default {
  name: 'child',
 methods:{
   SendData(){
     this.$emit('GetData','我是来自子组件用$emit传递的数据')
   }
 }
}
</script>

parent/parent/children

父子互通

:boom:通过parentparent和children就可以访问组件的实例,拿到实例代表什么?代表可以访问此组件的所有方法和data。(官方更推荐 props和events的传递方法)

:boom:要注意边界情况,如在#app上拿parent得到的是newVue()的实例,在这实例上再拿parent得到的是new Vue()的实例,在这实例上再拿parent得到的是undefined,而在最底层的子组件拿children是个空数组。也要注意得到children是个空数组。也要注意得到parent和children的值不一样,children的值不一样,children 的值是数组,而$parent是个对象

<template>
  <div>
    我是父组件: {{ data }}
    <button @click="ChangeChildData">改变子组件中的数据</button>
    <div>
      <child></child>
    </div>
  </div>
</template>
<script>
import child from "../views/Home.vue";
export default {
  name: "parent",
  data() {
    return {
      data: "000",
    };
  },
  components: {
    child,
  },
  methods: {
    ChangeChildData() {
      console.log(this.$children[0].message);
      this.$children[0].message = "被改变了";
    },
  },
};
</script>
++++++++++++++++++++++++++
<template>
  <div>
    <div>我是子组件:{{message}}</div>
    <button @click="ChangeParentData">改变父组件中数据</button>
  </div>
</template>

<script>
export default {
  name: 'child',
  data() {
    return {
      message:'123'
    }
  },
 methods:{
   ChangeParentData(){
     console.log(this.$parent)
     this.$parent.data = '被子组件改变了'
   }
 }
}
</script>

provide/inject

父传子孙

:boom:祖先元素通过provide选项提供变量,子孙元素可以通过inject选项获取祖先元素提供的变量数据(这里不论子组件嵌套有多深, 只要调用了inject 那么就可以注入provide中的数据)

//祖先
<template>
  <div>
    我是父组件: 我将通过provide向子孙组件传递数据
    <div>
      <child></child>
    </div>
  </div>
</template>
<script>
import child from "../views/Home.vue";
export default {
  name: "helloWorld",
  provide:{
    data:'传给子组件和孙组件的数据'
  },
  components: {
    child,
  },
};
</script>
+++++++++++++++++++++++++
//子组件
<template>
  <div>
    <div><h4>我是子组件home:{{data}}</h4></div>
    <about></about> 
  </div>
</template>

<script>
import about from './About.vue'
export default {
  name: 'home',
  inject:['data'],
  components:{
    about,
  },
}
</script>
+++++++++++++++++++++++++++
//孙组件
<template>
  <div>
    <h4>我是home组件的子组件about:{{data}}</h4>
  </div>
</template>
<script>
export default {
  name:'about',
  inject:['data']
}
</script>

ref/refs

父传子

:boom:ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例,可以通过实例直接调用组件的方法或访问数据

<template>
  <div>
    我是父组件: 我将通过ref获取子组件实例,数据:{{data}}
    <div>
      <child ref="home"></child>
    </div>
  </div>
</template>
<script>
import child from "../views/Home.vue";
export default {
  name: "helloWorld",
  data() {
    return {
      data:''
    }
  },
  components: {
    child,
  },
  mounted() {
    this.data = this.$refs.home.desc
    this.$refs.home.SendData()
  },
};
</script>
+++++++++++++++++++++++++
<template>
  <div>
    <div><h4>我是子组件home</h4></div>
  </div>
</template>

<script>
export default {
  name: 'home',
  data() {
    return {
      desc:'我是home组件的数据'
    }
  },
  methods: {
  SendData(){
    console.log(this.desc)
  }
},
  
}
</script>

eventBus

任意组件通信

:boom:思路:先创建出一个事件总局(eventBus),相当于第三方,所有组件都可以来这里存放事件,所有组件都可以来这里调用存放在这里的任何组件(不管是不是自己存放的事件)

:boom:缺点:当多个组件间需要共享数据时,事件的存放和调用就会显得很多,特别是过了一段时间再来维护项目时,很多事件你都不知道是在哪里提出又是在哪里调用的,不好维护

:o:创建事件总局

  1. 做成模块,要使用的时候导入(较好)
import Vue from 'vue'
const eventBus = new Vue();
export default eventBus;
  1. 直接将唯一的Vue实例绑定在Vue的原型上,通过this.$eventBus调用
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
  beforeCreate(){
    Vue.prototype.$eventBus = this
  },
  render: h => h(App)
}).$mount('#app')

:boom:以下用第一种创建总局方式实现兄弟间的通信示例

<template>
  <div>
    我是父组件: 让两个儿子实现相互通信
    <div>
      <home></home>
      <about></about>
    </div>
  </div>
</template>
++++++++++++++++++
<template>
  <div>
    <div>
      我是子组件home
      <button @click="SendData">向事件总局存放事件</button>
    </div>
  </div>
</template>
<script>
import eventBus from "../utils/eventBus";
export default {
  name: "home",
  data() {
    return {
      desc: "我是home组件存放在事件总局的事件提供的数据",
    };
  },
  methods: {
    SendData() {
      //事件名自定义
      eventBus.$emit("ShareData", this.desc);
    },
  },
  //销毁的时候移出存放的事件
  beforeDestroy() {
    eventBus.$off("ShareData", {});
  },
};
</script>
+++++++++++++++++++
<template>
  <div>
    <h4>我是home组件,后面是我从事件总局home兄弟组件存放的事件总获取的数据: {{data}}</h4>
  </div>
</template>
<script>
import eventBus from '../utils/eventBus'
export default {
  name:'about',
  data() {
    return {
      data:''
    }
  },
  mounted() {
    eventBus.$on('ShareData',value => {
      this.data = value
    })
  },
}
</script>

vuex

任意组件通信

:o:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。详情看Vuex篇

//store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    homeData:''
  },
  mutations: {
    SetHomeData(state,data){
      state.homeData = data
    }
  },
})

下面为兄弟组件通信

<template>
  <div>
    <div>我是home组件
      <button @click="SendData">点我存放数据</button>
    </div>
  </div>
</template>
<script>

export default {
  name: 'about',
  data () {
    return {
      data: '我来自home'
    }
  },
  methods: {
    SendData(){
      this.$store.commit('SetHomeData',this.data)
      console.log(this.$store)
    }
  },
  
}
</script>
++++++++++++++++++++++++++++++
<template>
  <div>
    <div>
      我是兄弟组件about {{data}}
      <button @click="GetData">点我获取vuex中home存放的数据</button>
    </div>
  </div>
</template>
<script>
export default {
  name: 'home',
  data () {
    return {
      data: ''
    }
  },
  methods: {
    GetData () {
      this.data = this.$store.state.homeData
    }
  },
}
</script>

localStorage/sessionStorage

任意组件

:boom:利用window.localStorage.setItem('key',value)和window.localStorage.getItem('key')可以达到存储数据的目的,和eventBus差不多都是一个第三方中介,缺点容易混乱,可以配合vuex使用可以做数据持久化,需要注意的是存的时候需要将对象转化成JSON格式,取出再转化为对象。

let obj = { name: 'zhangsan', age: 23 }
window.localStorage.setItem('zhangsan',JSON.stringify(obj))
console.log(JSON.parse(window.localStorage.getItem('zhangsan')))

inheritAttrs/$attrs

父传子孙

:boom:attrs,捡漏对象,当父组件传过来的变量,子组件中没有用props接收时都会被它所接收,this.attrs,捡漏对象,当父组件传过来的变量,子组件中没有用 props 接收时都会被它所接收,this.attrs 可访问,插槽也是,但父组件传过来HTML结构而子组件没有用 solt 坑位接收时,会被 solts捡漏,this.solts捡漏,this.solts可捡漏

:boom:默认情况下父作用域的不被认作 props 的 attribute 绑定 (attribute bindings) 将会“回退”且作为普通的 HTML attribute 应用在子组件的根元素上。当撰写包裹一个目标元素或另一个组件的组件时,这可能不会总是符合预期行为。通过设置 inheritAttrs 到 false,这些默认行为将会被去掉。而通过 (同样是 2.4 新增的) 实例 property $attrs 可以让这些 attribute 生效,且可以通过 v-bind 显性的绑定到非根元素上。

示例:

<template>
  <div>
    我是父组件: 让两个儿子实现相互通信
    <div>
      <home 
      :name='name'
      :age='age'
      :gender='gender'></home>
    </div>
  </div>
</template>
<script>
import home from '../views/Home.vue'
export default {
  name: 'helloWorld',
  components: {
    home,
  },
  data () {
    return {
      name: '张三',
      age:34,
      gender:'男',
    }
  }

}
</script>
++++++++++++++++++++++++++++++++++++
<template>
  <div id="root">
    <div>
      我是home组件,我用props只接收了name属性,其他传递过来的没接收,name为:{{name}}
      <!-- 可以用v-bind直接传递 -->
      <about v-bind='$attrs'></about> 
    </div>
  </div>
</template>
<script>
import about from './About.vue'
export default {
  name: 'home',
  //没接收的age和gender属性被放在了id=root的根标签上(如果不配置inheritAttrs:false的话
  props:{
    name:''
  },
  data () {
    return {
      data: ''
    }
  },
  inheritAttrs:false,//配置之后没用props接收的属性可以通过this.$attts拿到
  components:{
    about
  },
  mounted() {
    console.log(this.$attrs) //{age: 34, gender: '男'}
    
  },
}
</script>
++++++++++++++++++++
<template>
  <div>
    <div v-bind="$attrs">我是about组件,我只接收了age属性:{{age}}
      <button @click="SendData">我是$attrs:{{$attrs}}</button>
    </div>
  </div>
</template>
<script>

export default {
  name: 'about',
  props:{
    age:0
  },
  inheritAttrs:false,
  data () {
    return {
      data: '我来自home'
    }
  },
  methods: {
    SendData(){
      this.$store.commit('SetHomeData',this.data)
      console.log(this.$store)
    }
  },
  
}
</script>

Vue3组件通信

:boom:详情请看官方文档的组件基础和组件深入

父传子

props

<!-- parent.vue -->
<template>
    <child1 :message1 = "forChild1"/>
</template>

<script setup lang="ts">
import { ref } from "vue";
import child1 from "./Child1.vue";

let forChild1 = ref('message for child1')
</script>

<!-- child1.vue -->
<template>
    <div>
        Child1——来自父元素内容:{{message1}}
    </div>
</template>

<script setup lang="ts">
defineProps(['message1'])
</script>

provide/inject

:boom:祖先元素可以向所有后代元素传递信息

<!-- parent -->
<template>
    <child1/>
</template>

<script setup lang="ts">
import { provide } from "vue";
import child1 from "./Child1.vue";

provide('fromParent',{origin:'parent',message:'我来自祖先元素'})
</script>

<!-- child1 -->
<template>
  <div>
    Child1——来自父元素内容:{{
      fromParent ? fromParent.message : '暂时未接收到来自父元素的信息'
    }}
  </div>
</template>

<script setup lang="ts">
import { inject } from 'vue'

interface FromParent{
    origin: string,
    message: string
}
const fromParent  = inject('fromParent') as FromParent
</script>

attrs

:boom: 父元素绑定在子组件身上的所有属性和事件,除了class和style和背子组件用props接收的外,其他的都可以用 attrs 接收到。如果子组件模板中只有一个根元素,又没有用props和emit接收,那就会穿透绑定到该根元素上,如果该子组件不止一个根元素,则会发出警告

<!-- parent -->
<template>
  <child1 :mes1="'myChild1'" :mes2="'myChild2'" @event1="handel1" @event2="handel1" />
</template>

<script setup lang="ts">
import child1 from './Child1.vue'

function handel1(){console.log('我是通过@click传给子元素的方法2')}
function handel2(){console.log('我是通过@click传给子元素的方法2')}

</script>

<!-- child1 -->
<template>
  <div>
    Child1——来自父元素内容:{{ mes1 ? mes1 : '暂时未接收到来自父元素的信息' }}
  </div>
</template>

<script setup lang="ts">
import { useAttrs } from 'vue'

defineProps(['mes1'])
const attrs = useAttrs()
console.log(attrs) 
//输出 Proxy {mes2: 'myChild2', __vInternal: 1, onEvent1: ƒ, onEvent2: ƒ}
</script>

<!-- 如果不是 <script setup>模式 -->
<script>
export default {
  setup(props, ctx) {
    // 透传 attribute 被暴露为 ctx.attrs
    console.log(ctx.attrs)
  }
}
</script>

子传父

emit

<!-- parent -->
<template>
  <child1 @Event1="event1" />
  <div>
    parent——来自child1的信息:{{
      fromChild1 ? fromChild1 : '目前没有收到来自child1的信息'
    }}
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import child1 from './Child1.vue'

let fromChild1 = ref('')
function event1(message: string) {
  fromChild1.value = message
}
</script>

<!-- child1 -->
<template>
  <div>
    <div>
      <button @click="$emit('event1', 'hello, 我来自child1')">
        点击向父元素传递信息
      </button>
    </div>
  </div>
</template>

<script setup lang="ts">
defineEmits(['event1'])
</script>

expose/ref

详情

<!-- parent 需要注意的是:由于生命周期调用的顺序关系,最开始myChild1.value值为null-->
<template>
  <child1 ref="myChild1" />
  <div><button @click="handle">输出来自child1的expose</button></div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import child1 from './Child1.vue'

const myChild1 = ref(null)
function handle() {
  console.log(myChild1.value)
}
</script>

<!-- child1 -->
<script setup lang="ts">
defineExpose({message:'我来自child1'})
</script>

任意组件通信

mitt

:boom:因为vue3没有了eventBus,所以有叫mitt的包代替 先安装 npm i mitt -S

import mitt from 'mitt'

const mymitt = mitt()
export default mymitt
<!-- parent -->
<template>
  <child1 />
  <div>{{childMessage}}</div>
</template>

<script setup lang="ts">
import child1 from './Child1.vue'
import { ref,onUnmounted } from 'vue'
import mitt from '../utils/mitt'

let childMessage = ref('')
mitt.on('handleChange',handle)
function handle(message:any){
    childMessage.value = message
}

onUnmounted(()=>{
    mitt.off('handleChange',handle)
})
</script>

<!-- child1 -->
<template>
  <button @click="handle">调用绑定在mitt身上的事件</button>
</template>

<script setup lang="ts">
import mitt from '../utils/mitt'
function handle() {
  mitt.emit('handleChange', '我来自child1')
}
</script>

pinia

:boom:相当于Vuex5.0,去除了mutation,只有 state、getters、actions

:boom: npm i pinia

// main.ts
import { createPinia } from 'pinia'
const pinia = createPinia()
app.use(pinia)
// parentStore.js
import { defineStore } from "pinia";

export const useParentStore = defineStore('parent',{
    state: () => {
        return{
            message1: 'child1',
            message2: 'child2'
        }
    },
    getters:{
        getMes1(state){
            return '我来自parentStore ' + state.message1
        },
        getMes2(state){
            return '我来自parentStore ' + state.message2
        }
    },
    actions:{
        updateMessage1(tartget:any){
            console.log(tartget)
            this.message1 = tartget
        },
        updateMessage2(tartget:any){
            this.message2 = tartget
        }
    }
})
<!-- child1.vue -->
<template>
    <div>{{getMes1}}</div>
  <button @click="handle">调用parentStore上的actions事件</button>
</template>

<script setup lang="ts">
import { storeToRefs } from "pinia";
import { useParentStore } from "../store/parent";

// 想要解构出的变量有响应,必须借助storeToRefs辅助函数
const parentStore = useParentStore()
const { getMes1 } = storeToRefs(parentStore)
function handle() {
    parentStore.updateMessage1('我被child1修改了')
}
</script>
<template>
    <div>{{getMes2}}</div>
  <button @click="handle">调用parentStore上的actions事件</button>
</template>

<script setup lang="ts">
import { storeToRefs } from "pinia";
import { useParentStore } from "../store/parent";

// 想要解构出的变量有响应,必须借助storeToRefs辅助函数
const parentStore = useParentStore()
const { getMes2 } = storeToRefs(parentStore)
function handle() {
    parentStore.updateMessage2('我被child2修改了')
}
</script>