likes
comments
collection
share

一文读懂Vue组件通信

作者站长头像
站长
· 阅读数 4

一文读懂Vue组件通信

props

props通常用来进行 父 -> 子 之间的组件通信

通信内容也可以是一个组件当中的方法

常规使用方式如下

组合式API

<script setup>
  import ChildA from '@/components/ChildA.vue'

  import { ref } from 'vue'

  const count = ref(0)
</script>

<template>
  <div class="parents">
    <h1>父组件</h1>
    <ChildA :count="count"/>
  </div>
</template>
<script setup>
  defineProps({
    count: {
      type: Number,
      default: 0
    }
  })
</script>

<template>
  <div class="child_a">
    <h2>ChildA-{{ count }}</h2>
  </div>
</template>

选项式API

<template>
  <div class="parents">
    <h1>父组件</h1>
  <ChildA :count="count"/>
  </div>
</template>

<script>
import ChildA from '@/components/ChildA.vue'
export default {
  name: "Parents",
  data(){
    return {
      count: 1
    }
  },
  components: {
    ChildA
  }
}
</script>
<template>
  <div class="child-a">
    <h2>child-a-{{ count }}</h2>
  </div>
</template>

<script>
export default {
  name: "ChildA",
  props: {
    count: {
      type: Number,
      default: 0
    }
  }
}
</script>

自定义事件:event

自定义事件,通常用来进行 父 -> 子 之间的事件传递

发生在子组件用来告知父组件状态变更,触发一定的代码逻辑或者是传递部分参数

常规使用方式如下

组合式API

<script setup>
import { ref } from 'vue'
import ChildA from '@/components/ChildA.vue'

function parentEvent(){
  alert(`父组件传递过来的方法被点击${count.value}次`)
  count.value++
}

const count = ref(0)
</script>
<template>
  <div class="example">
    <h1>Parents</h1>
    <ChildA :count="count" @parentEvent="parentEvent"/>
  </div>
</template>
<script setup>
defineProps({
  count: {
    type: Number,
    default: 0
  }
})

const emit = defineEmits(['parentEvent'])

function onRowClick(){
  emit('parentEvent')
}
</script>
<template>
  <div class="child-a">
    <h2 @click="onRowClick">Child-A-{{ count }}</h2>
  </div>
</template>

选项式API

<template>
  <div class="parents">
    <h1>父组件</h1>
  <ChildA :count="count" @parentEvent="parentEvent"/>
  </div>
</template>

<script>
import ChildA from '@/components/ChildA.vue'
export default {
  name: "Parents",
  data(){
    return {
      count: 1
    }
  },
  components: {
    ChildA
  },
  methods: {
    parentEvent(){
      alert(`父组件传递过来的方法被点击${this.count}次`)
      this.count++
    }
  }
}
</script>
<template>
  <div class="child-a">
    <h2 @click="onRowClick">child-a-{{ count }}</h2>
  </div>
</template>

<script>
export default {
  name: "ChildA",
  props: {
    count: {
      type: Number,
      default: 0
    }
  },
  methods: {
    onRowClick(){
      this.$emit('parentEvent')
    }
  }
}
</script>

provide / inject

provide 和 inject 通常用来处理跨组件之间的通信

通过props也能实现,但是会造成参数跨越多层级组件穿透传递

一文读懂Vue组件通信

组合式API

<script setup>
import { ref, provide } from 'vue'
import ChildA from '@/components/ParentsV3/ChildA.vue'

const count = ref(0)
  
// 提供响应值
provide('count',count)
// 提供静态值
provide('msg','provide传递过来的数据')
</script>
<template>
  <div class="example">
    <h1>Parents</h1>
    <ChildA />
  </div>
</template>
<script setup>
import GrandChild from '@/components/ParentsV3/GrandChild.vue'
</script>

<template>
  <div class="child-a">
    <h2>Child-A</h2>
    <GrandChild />
  </div>
</template>
<script setup>
import { inject } from 'vue'
const msg = inject('msg')
const count = inject('count')
</script>
<template>
  <div class="example">
      V3孙子组件 --- msg-{{ msg }} ---count-{{ count }}
  </div>
</template>

选项式API

注意:在选项式API中,provide 默认提供的值是非响应式的,如果需要传递响应式的数据,需要通过 computed 方法返回响应式数据

<template>
  <div class="parents">
    <h1>父组件</h1>
    <ChildA :count="count" @parentEvent="parentEvent"/>
  </div>
</template>

<script>
import ChildA from '@/components/ParentsV2/ChildA.vue'
import { computed } from 'vue'
export default {
  name: "Parents",
  data(){
    return {
      count: 1
    }
  },
  provide(){
    return {
      msg: 'v2-provide传递过来的数据',
      // count: this.count 这样写是非响应式
      count: computed(() => this.count)
    }
  },
  components: {
    ChildA
  },
}
</script>
<template>
  <div class="child-a">
    <h2>child-a</h2>
    <GrandChild />
  </div>
</template>

<script>
import GrandChild from '@/components/ParentsV2/GrandChild.vue' 
export default {
  name: "ChildA",
  components: {
    GrandChild
  }
}
</script>
<template>
  <div class="example">
    V2孙子组件--msg--{{ msg }}---count---{{ count }}
  </div>
</template>

<script>
export default {
  name: "GrandChild",
  inject: ['msg','count'],
}
</script>

模拟Vuex(了解)

基与provide / inject 可以无视组件层级进行通信的背景下,我们可以模拟出 Vuex 的功能

试想一下,在一个 Vue 应用中,所有组件的根组件都是 APP.vue

所以当我们在 APP.vue 中通过 provide 暴露出去 state的时候,就是全局共用的状态了

如果我们暴露出去的是 APP.vue 的 this呢?

然后再通过 minxin 插入到APP.vue中,就实现了一个简单版的 Vuex

注意:Vue3 已经不再建议使用 minxin,以及拿不到this了,以下内容了解即可,使用Vue2 语法示例

一文读懂Vue组件通信

<script>
import Parents from './components/Parents.vue'
import appMixin from './mixins/index'

export default {
  mixins: [appMixin],
  components: {
    Parents
  },
  provide(){
    return {
      app: this
    }
  }
}
</script>

<template>
  <div class="wrapper">
    <Parents/>
  </div>

</template>
export default {
    data(){
        return {
            msg: 'Hello Vue3',
        }
    }
}
<script setup>
import Child from './Child.vue'
</script>

<template>
  <div class="greetings">
    <Child />
  </div>
</template>
<template>
    <div class="example">
        <div>Child----{{ app.msg }}</div>
    </div>
</template>
<script>
export default {
    inject: ['app'],
}
</script>

找到任意组件实例

Vue3在选项式API中保留了 组件实例调用,理论上基于 parent和parent 和 parentchildren,我们可以找到 APP.vue上面所有的组件实例

注意:下面案例中的 父组件范指上级组件,子组件范指下级组件

最近的指定父组件

// 由一个组件,向上找到最近的指定父组件
function findComponentUpward(context, componentName) {
  let parent = context.$parent;
  let name = parent.$options.name;

  while (parent && (!name || [componentName].indexOf(name) < 0)) {
    parent = parent.$parent;
    if (parent) name = parent.$options.name;
  }
  return parent;
}

export { findComponentUpward }
<template>
    <div class="example">
        <h1>Parents组件</h1>
        <Child />
    </div>
</template>
<script>
import Child from './Child.vue'
export default {
    name: 'Parents',
    components: {
        Child
    },
    data() {
        return {
            parentMsg: '父组件状态'
        };
    },
}
</script>
<template>
    <div class="example">
        <h2>Child</h2>
        <div>{{ upwardComp ? upwardComp.parentMsg : '' }}</div>
    </div>
</template>
<script>
import { findComponentUpward } from '@/utils/index';
export default {
    name: 'Child',
    data(){
        return {
            upwardComp: null
        }
    },
    mounted(){
        this.upwardComp = findComponentUpward(this,'Parents')
    }
}
</script>

所有的指定父组件

//由当前组件开始,向上找到所有的指定父组件
function findComponentsUpward (context, componentName) {
  let parents = [];
  const parent = context.$parent;

  if (parent) {
    if (parent.$options.name === componentName) parents.push(parent);
    return parents.concat(findComponentsUpward(parent, componentName));
  } else {
    return [];
  }
}
export { findComponentsUpward };

最近的指定子组件

// 由一个组件,向下找到最近的指定组件
function findComponentDownward (context, componentName) {
  const childrens = context.$children;
  let children = null;

  if (childrens.length) {
    for (const child of childrens) {
      const name = child.$options.name;

      if (name === componentName) {
        children = child;
        break;
      } else {
        children = findComponentDownward(child, componentName);
        if (children) break;
      }
    }
  }
  return children;
}
export { findComponentDownward };

所有的指定子组件

// 由一个组件,向下找到所有指定的组件
function findComponentsDownward (context, componentName) {
  return context.$children.reduce((components, child) => {
    if (child.$options.name === componentName) components.push(child);
    const foundChilds = findComponentsDownward(child, componentName);
    return components.concat(foundChilds);
  }, []);
}
export { findComponentsDownward };

所有指定组件的兄弟组件

// 由一个组件,找到指定组件的兄弟组件
// 第三个参数用来控制是否包含自己
function findBrothersComponents (context, componentName, exceptMe = true) {
  let res = context.$parent.$children.filter(item => {
    return item.$options.name === componentName;
  });
  let index = res.findIndex(item => item._uid === context._uid);
  if (exceptMe) res.splice(index, 1);
  return res;
}
export { findBrothersComponents };

EventBus

EventBus 常用来在两个没有关联之间的页面进行通信,或用在跨多层级之间的组件通信

全局初始化

import Vue from 'vue'

Vue.prototype.$EventBus = new Vue()

引入初始化

import Vue form 'vue'

export const EventBus = new Vue();

接收事件

<template>
  <div>
    {{ msg }}
  </div>
</template>

<script>
  export default {
    data(){
      return {
        msg: ''
      }
    },
    mounted(){
      this.$EventBus.$on('testEvnet',(msg) => {
        this.msg = msg;
      })
    }
  }
</script>

触发事件

<template>
  <div>
    <button @click="onBtnClick">EventBus Demo</button>
  </div>
</template>

<script>
  export default {
   methods: {
     onBtnClick(){
       this.$EventBus.$emit('testEvent','测试EventBus消息接收')
     }
   }
  }
</script>

Pinia

可参考笔者高赞文章

转载自:https://juejin.cn/post/7342752740371431464
评论
请登录