面试总结-你真的了解vue组件之间的通信吗?

前言

在本人最近的面试中,关于vue之间的组件通信的方式是经常被面试官提起,随着面试场次的增多,本人对于组件通信方式的理解 从只会 'props'、'emit'和vuex 之外学到了更多的方式。

为什么要有组件通信

我们都知道,在vue中当我们需要将当前页面组件的信息传递给另一个组件的时候,就需要实现vue的组件通信功能。因为Vue是一个组件化的框架,应用程序通常被拆分成许多小组件,每个组件负责一个特定的功能。组件通信允许这些组件相互交流和协作,使得构建复杂的应用程序变得更加容易和灵活。

因此组件通信在Vue中是必需的,因为它使得构建复杂的应用程序变得更加容易和灵活,具体原因包括:

  1. 组件化开发:Vue是一个组件化的框架,应用程序通常被拆分成许多小组件,每个组件负责一个特定的功能。组件通信使得这些组件能够相互交流和协作,从而构建出功能丰富的应用程序。
  2. 解耦和复用:通过组件通信,可以将应用程序拆分成独立的组件,每个组件只关注自己的功能,不需要关心其他组件的实现细节。这样可以降低组件之间的耦合度,提高组件的复用性和可维护性。
  3. 状态管理:组件通信使得状态管理变得更加简单和灵活。通过向子组件传递props和监听子组件的事件,可以实现父子组件之间的数据传递和状态管理。而通过全局事件总线、Vuex等工具,可以实现任意组件之间的状态共享和管理。
  4. 用户交互:在用户交互方面,组件通信可以实现组件之间的消息传递和事件触发。例如,一个按钮组件可以触发一个点击事件,通知其他组件执行相应的操作。
  5. 跨组件通信:有时候需要在不相邻的组件之间进行通信,例如兄弟组件之间或者跨级组件之间。通过提供/注入、全局事件总线、Vuex等方式,可以实现跨组件的数据传递和状态管理。

组件通信的方式

那么都有哪些方式可以实现组件通信呢?本人在最近的学习中共学习到了以下的实现方式:

  1. 父子组件通信:父组件通过props向子组件传递数据,子组件通过defineProps接收数据
  2. 父子组件通信:父组件provide,子组件inject
  3. 子父组件通信:子组件拿到父组件的数据并修改后emit出来,父组件通过v-model实现双向绑定
  4. 子父组件通信:子组件defineExpose暴露方法,父组件通过ref读取整个子组件对象来获取值
  5. 子父组件通信:子组件通过emit触发事件,父组件通过v-on(@) 监听事件
  6. EventBus 事件总线 vue3中不推荐使用,但是可以通过 mitt 插件实现
  7. vuex

下面本人将为大家讲解一下如何用以上方式来实现组件通信

父子组件通信

首先就是父子组件通信方式的实现了。

props

如以上代码

vue 复制代码
// 父组件
<template>
  <div class="hd">
    <input type="text" v-model="msg">
    <button @click="add">添加</button>
  </div>


  <Child :list="list"/>

</template>

<script setup>
import { ref } from 'vue'
import Child from './components/Child_1.vue'

const list = ref(['html', 'css'])

const msg = ref('')
const add = () => {
  list.value.push(msg.value)
  msg.value = ''
}

</script>

<style lang="css" scoped>

.hd input{
  width: 200px;
  padding: 10px 0;
  font-size: 10px;
}
.hd button{
  padding: 10px 20px;
  font-size: 20px;
}

</style>
vue 复制代码
//子组件
<template>
  <div class="child">
    <div class="bd">
      <ul>
        <li v-for="item in list" :key="item">{{ item }}</li>
      </ul>
    </div>
  </div>
</template>

<script setup>
import { defineProps } from 'vue'

defineProps({
  list: {
    type: Array,
    default: () => []
  }
})
</script>

<style scoped>
.child {
  margin-top: 20px;
}
.bd {
  background-color: #f0f0f0;
  padding: 20px;
  border-radius: 8px;
}
</style>

以上代码中,在父组件中,我们通过<Child :list="list"/>语法,将父组件的list数据传递给子组件。并且使用 ref 创建了名为 list 、msg和 add 的响应式数据。list 用于存储列表项,msg 用于存储输入框的值,add 函数用于向 list 数组中添加新的列表项。

子组件中,通过defineProps定义了一个list属性,其类型为数组,默认值为空数组。然后在模板中使用了v-for指令遍历list数组,将数组中的每个元素渲染成一个列表项。

在父组件中,通过将子组件Child的list属性绑定到父组件中的msg变量,实现了父组件向子组件传递数据的功能。当父组件中的msg变量发生变化时,子组件中的list属性也会相应地更新。

provide和inject

在vue中,我们可以通过导入provide和inject,开实现父子组件之间的通信。
特点

  1. 灵活性: provide 和 inject 不受组件层次结构的限制,可以在任何深度的组件中使用,从而使得数据传递更加灵活。
  2. 跨层级通信: 与 props/downward data flow 不同,provide 和 inject 可以实现跨层级的组件通信,无需一层层地传递 props,从而简化了组件之间的通信。
  3. 依赖注入的思想: 类似于依赖注入的思想,使得组件之间的依赖关系更加清晰,降低了组件之间的耦合度。
vue 复制代码
<template>
  <div class="hd">
    <input type="text" v-model="msg">
    <button @click="add">添加</button>
  </div>


  <!-- <Child :list="list"/> -->
  <Child />

</template>

<script setup>
import { ref,provide } from 'vue'
import Child from './components/Child_5.vue'


const msg = ref('')
const list = ref(['html', 'css'])

provide('list', list.value);

const add = () => {
  list.value.push(msg.value)
  msg.value = ''
}

</script>

<style lang="css" scoped>
同上
</style>
vue 复制代码
<template>
  <div class="bd">
    <ul>
      <li v-for="item in list">{{item}}</li>
    </ul>
  </div>
</template>

<script setup>
import { ref,inject } from 'vue'

const list = inject('list')

</script>

<style lang="css" scoped>

</style>

在以上代码中,我们通过父组件 provide 提供了 list 数据,而子组件则通过 inject 获取了这个数据,从而实现了组件之间的通信。这种方式可以在组件层次结构中的任何深度进行通信,而不需要一层层地传递 props,因此更加灵活方便。

但是需要注意的是它只能由祖先组件向子孙组件发送数据。并且不太推荐使用这种方式来实现父子组件之间的通信,因为它与我们所认知的单向数据流的思想相悖。

子父组件通信

emit

vue 复制代码
<template>
  <Child @add="handle"/>

  <div class="bd">
    <ul>
      <li v-for="item in list">{{item}}</li>
    </ul>
  </div>

</template>

<script setup>
import { ref } from 'vue';
import Child from './components/Child_2.vue'

const list = ref(['html', 'css'])

const handle = (e) => {
  list.value.push(e)
}
</script>
vue 复制代码
<template>
  <div class="hd">
    <input type="text" name="" id="" v-model="msg">
    <button @click="add">添加</button>
  </div>

</template>

<script setup>
import { ref } from 'vue';

const msg = ref('')
const emits = defineEmits(['add']) //创建一个add事件

const add = () => {
  emits('add', msg.value);
  console.log(msg.value);
  msg.value = '';
}

</script>

在以上代码中,通过 defineEmits 函数来定义了一个名为 add 的自定义事件。这个函数的作用是创建一个函数,用于触发组件的自定义事件。

具体来说,在子组件中,我们首先使用 defineEmits 函数来创建了一个名为 emits 的对象,该对象包含了定义的自定义事件。然后,在子组件的 add 方法中,我们通过 emits 对象来触发了 add 事件,并且传递了 msg.value 作为参数。

这样,在父组件中,我们通过监听子组件触发的 add 事件,并在事件处理函数中接收到传递过来的参数,从而实现了子组件向父组件的通信。

v-model

我们都知道v-model 在 Vue.js 中通常用于实现表单元素和组件之间的双向数据绑定,但它也可以用于自定义组件之间的通信。这是因为 v-model 实际上是一个语法糖,它背后的原理是通过将 value 属性和 input 事件结合起来实现数据的双向绑定。

当你在一个自定义组件上使用 v-model 时,Vue.js 实际上会做两件事情:

  1. 将 value 属性传递给组件作为 props。
  2. 监听组件内部触发的 input 事件,并根据该事件更新外部数据。

因此,通过在自定义组件内部正确地处理 value 属性和触发 input 事件,我们可以使自定义组件支持 v-model 语法,并实现与父组件之间的双向数据绑定。

vue 复制代码
<template>
  <Child v-model:list="list"/>

  <div class="bd">
    <ul>
      <li v-for="item in list">{{item}}</li>
    </ul>
  </div>

</template>

<script setup>
import { ref } from 'vue';
import Child from './components/Child_3.vue'

const list = ref(['html', 'css'])

</script>
vue 复制代码
<template>
  <div class="hd">
    <input type="text" name="" id="" v-model="msg">
    <button @click="add">添加</button>
  </div>

</template>

<script setup>
import { ref } from 'vue';

const msg = ref('')
const props = defineProps({
  list: {
    type: Array,
    default: () => []
  }
})

const emits = defineEmits(['update:list']) // 事件名必须是 update:xxx

const add = () => {
  const arr = props.list
  arr.push(msg.value)
  // 不要写这种代码:props.list.push(msg.value)

  emits('update:list', arr)
  msg.value = ''
}

</script>

以上代码中,我们在父组件中,通过 <Child v-model:list="list"/> 这样的语法,将 list 数据作为 v-model 的值传递给了子组件 Child。这样做的效果是,父组件的 list 数据会被传递给子组件,并且在子组件内部使用 v-model 时,list 数据将作为 value 属性的值。

在子组件 Child 中,使用了 v-model="msg",这样做的效果是,msg 数据将与输入框进行双向绑定。也就是说,当输入框中的值发生变化时,msg 数据也会跟着变化;反之亦然。

在子组件中,通过点击按钮触发 add 方法,将输入框中的值添加到 list 数组中。同时,通过 emits('update:list', arr) 将更新后的 list 数组发送给父组件。

在父组件中,由于使用了 v-model,因此子组件发送的 list 数据更新会立即反映在父组件中,从而实现了双向数据绑定和组件通信的效果。

defineExpose

实际上defineExpose并不是专门用于实现组件通信的功能,而是 Vue 3 中的一个用于暴露组件内部数据或方法给父组件的 API。在 Vue 3 中,组件的内部数据和方法默认是私有的,只能在组件内部访问,而无法直接通过组件实例访问。但是,有时候我们希望将某些数据或方法暴露给父组件,以便父组件可以直接访问或操作。

defineExpose 的作用就是将组件内部的数据或方法暴露给父组件。当组件使用 defineExpose 声明后,父组件就可以通过组件实例直接访问这些暴露的数据或方法,从而实现了组件之间的通信。

vue 复制代码
<template>
  <Child ref="childRef"/>

  <div class="bd">
    <ul>
      <li v-for="item in childRef?.list">{{item}}</li>
    </ul>
  </div>

</template>

<script setup>
import { ref } from 'vue';
import Child from './components/Child_4.vue'
const childRef = ref(null)
</script>
vue 复制代码
<template>
  <div class="hd">
    <input type="text" name="" id="" v-model="msg">
    <button @click="add">添加</button>
  </div>

</template>

<script setup>
import { ref } from 'vue';

const list = ref(['html', 'css'])
const msg = ref('')

const add = () => {
  list.value.push(msg.value)
  msg.value = ''
}

defineExpose({list}) //子组件暴露出去的数据

</script>

以上代码中,我们在子组件中通过 defineExpose 来暴露子组件的 list 数据,这样父组件就可以直接通过 childRef.list 访问子组件的 list 数据了

兄弟组件通信

EventBus 事件总线

事件总线是一种常见的 Vue 应用程序中组件通信的方式,它通过一个中央事件总线来实现组件之间的通信,允许不同组件之间进行解耦合的通信。

vue 复制代码
//App.vue
<template>
  <div>
    <ChildA />
    <ChildB />
  </div>
</template>

<script setup>
import ChildA from './components/child6/A.vue'
import ChildB from './components/child6/B.vue'

</script>
vue 复制代码
//A.vue
<template>
  <div>
    <input type="text" v-model="msg" />
    <button @click="add">添加</button>
  </div>
</template>

<script setup>
import emitter from './emit.js'
import { onMounted, ref } from "vue";

const list = ref(["html", "css"]);

onMounted(() => {
  emitter.emit("list", list.value);
});

const msg = ref("");
const add = () => {
  list.value.push(msg.value);
  msg.value = "";
};
vue 复制代码
//B.vue
<template>
  <div>
    <ul>
      <li v-for="item in list ">{{ item }}</li>
    </ul>
  </div>
</template>

<script setup>
import emitter from './emit';
import { ref } from 'vue';

const list = ref([])
emitter.on('list', (e) => {
  console.log(e);
  list.value = e;
})

</script>
js 复制代码
// emit.js
import mitt from 'mitt';

const emitter = mitt();

export default emitter;

首先,因为在vue3中不支持直接使用Eventbus,我们需要通过import mitt from 'mitt' 来创建事件总线。需要注意的是我们需要去下载这个mitt插件。

在 ChildA 组件中,当组件挂载完成时,我们通过事件总线向外发送了一个名为 list 的事件,并携带了自己的 list 数据。这里的 emitter.emit("list", list.value) 表示发送一个名为 list 的事件,并将当前 list 数据作为参数传递给这个事件。

在 ChildB 组件中,它监听了事件总线上的 list 事件,并在事件被触发时执行相应的回调函数,更新自己的数据。这里的 emitter.on('list', callback) 表示监听名为 list 的事件,并在事件被触发时执行 callback 回调函数。在回调函数中,它将收到的数据更新到 ChildB 组件的 list 中。

的来说,通过事件总线,ChildA 组件发送事件并携带数据,而 ChildB 组件监听这个事件,并在事件被触发时接收数据,从而实现了组件之间的通信。

vuex

在vue中我们可以通过vuex来实现组件之间的通信。因为Vuex 是 Vue.js 官方提供的状态管理库,它可以帮助我们集中管理应用中的状态,并提供了一种在组件之间共享状态的机制。

  1. 安装 Vuex: 首先,我们需要去安装 Vuex。

    bash 复制代码
    npm install vuex
    # 或者
    yarn add vuex
  2. 创建 Vuex Store: 在文件夹中,我们需要创建一个 store 文件来管理应用的状态。在 Vuex store 中,我们可以定义状态、mutations、actions 等。

    js 复制代码
    // store/index.js
    import { createStore } from 'vuex';
    
    const store = createStore({
      state: {
        list: ['html', 'css'],
      },
      mutations: {
        updateList(state, newList) {
          state.list = newList;
        },
      },
      actions: {
        updateList(context, newList) {
          context.commit('updateList', newList);
        },
      },
      getters: {
        getList(state) {
          return state.list;
        },
      },
    });
    
    export default store;
  3. 使用 Vuex: 将创建的 Vuex store 导入到 main.js 中,以便所有组件都能够访问到 Vuex 中的状态和方法。

    js 复制代码
    // main.js
    import { createApp } from 'vue';
    import App from './App.vue';
    import store from './store';
    
    const app = createApp(App);
    app.use(store);
    app.mount('#app');
  4. 在兄弟组件中使用 Vuex:

    • ChildA 组件:在需要更新状态的地方,通过调用 Vuex store 中的 action 来更新状态。

      javascript 复制代码
      // ChildA.vue
      <template>
        <button @click="updateList">Update List</button>
      </template>
      
      <script>
      import { mapActions } from 'vuex';
      
      export default {
        methods: {
          ...mapActions(['updateList']),
        },
      };
      </script>
    • ChildB 组件:通过计算属性(computed property)或者直接访问 Vuex store 的 getters 来获取状态。

      javascript 复制代码
      // ChildB.vue
      <template>
        <div>
          <ul>
            <li v-for="item in list" :key="item">{{ item }}</li>
          </ul>
        </div>
      </template>
      
      <script>
      import { mapGetters } from 'vuex';
      
      export default {
        computed: {
          ...mapGetters(['getList']),
          list() {
            return this.getList;
          },
        },
      };
      </script>

通过以上步骤,我们就可以在兄弟组件之间实现通信了。当 ChildA 组件触发更新时,通过 Vuex store 中的 action 更新状态,然后 ChildB 组件通过计算属性或者直接访问 getters 来获取更新后的状态,从而实现了兄弟组件之间的通信。

总结

我所了解的组件之间的通信方式就是以上的所有内容了,如有不足之处,欢迎大家加以补充。

相关推荐
兰亭妙微UI设计公司19 分钟前
兰亭妙微ui设计公司分享:B端组件命名如何支持团队协作
前端·javascript·vue.js
云和数据.ChenGuang43 分钟前
光哥说Python 异常深度解析:从异常模型到工程化实践
java·前端·人工智能·python·langchain
小大宇1 小时前
Vue知识点-ElementPlus-03提示框+询问+通知
前端·javascript·vue.js
步行cgn2 小时前
Axios 请求成功但页面不显示:两个前端错误的分析与修正
前端
时间的情敌2 小时前
WebGIS + OpenLayers + GeoServer + PostGIS #量算 #点聚合
前端·geoserver·openlayers
Lichee203 小时前
招行Java 面试知识点梳理:Spring 事务、锁、线程池、集合
java·面试
不可能片场3 小时前
Electron 发布 cookie 库先查明文再写解密
前端·electron
郭小李3 小时前
springmvc 拦截器
java·开发语言·前端
web打印社区3 小时前
Chrome-kiosk-printing能上生产吗
开发语言·前端·javascript·chrome·websocket
白帽攻防录3 小时前
某pc客户端app从xss到rce实战案例
前端·网络安全·黑客·xss·rce·src漏洞·app漏洞