likes
comments
collection
share

面试官问我EventBus,我邪魅一笑,竟让面试官大惊失色

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

最近一直在西安找工作,记录一下我常碰见的面试题吧,顺便:求内推!!!

面试官:Vue组件通讯都有哪些方法。

这种题应该属于最简单的Vue八股文了,我们一个点一个点开始解析。先说一下答案,由于现在我基本上以vue3为主,所以示例代码都是vue3写法

  • Props
  • $emit事件触发
  • Ref模版引用
  • $parent$root
  • attrslisteners
  • provideinject
  • Vuex 或者 Pinia
  • 本文中主要讨论的EventBus

答案解析

Props

props就是最基础的父子组件传值,由父组件通过v-bind或者:直接给子组件,子组件接收的时候最好限制一下类型,做一些判断。我写个基本demo吧

  • 父组件
<template>
    <child :b="b"/>
</template>
<script setup>
    const b = ref(1)
</script>
  • 子组件
<template>
    <p>{{props.b}}</p>
</template>
<script setup>
    const props = defineProps({
        b:{
            type: Number
        }
    })
</script>

$emit

$emit一般用于子组件向父组件传值,由父组件通过v-on或者@来监听子组件的事件,子组件则通过 $emit触发这个事件来通讯。

  • 父组件
<template>
    <child @test="test" />
</template>
<script setup>
    const test = (a)=>{
        console.log(a)
    }
</script>
  • 子组件
<script setup>
    const emits = defineEmits(['test'])
    emits('test','test emited')
</script>

Ref模版引用

这里的ref不是指响应式变量,而是获取子组件的实例或者是子组件的虚拟DOM。获取到子组件的实例,就可以直接修改值或者调用方法,这样也实现了通讯,但是不推荐这种方式,因为破坏了单向数据流。

 <template>
     <child ref="child" />
 </template>
 <script setup>
     const child=ref(null)
     onMounted(()=>{
         console.log(child.value.a)
         child.value.test('2')
         console.log(child.value.a)
     })
     
 </script>
  • 子组件
 <template>
    <p> {{ a }}</p>
</template>
<script setup>
    const a = ref(1)
    const test = (val)=>a.value=val
    defineExpose({a,test})
</script>

$parent$root

在vue2还有vue3的optionsAPI中,我们可以直接使用this.$parentthis.$root,但是在vue3的组合式API中貌似没有这个了,但是我们可以通过getCurrentInstance来获取到,但是这种方式依旧不推荐,因为破坏了单向数据流。

<script setup>
    const {parent,root} = getCurrentInstance()
</script>

attrslisteners

这两个属性本质上其实还是props和emit那一套,只不过我们不用在子组件里声明,可以直接使用,大部分用于组件库这种,有很多属性的情况,代码我就不再赘述了。

provideinject

这里其实是Vue提供的一种依赖注入的方式,可以把值其他东西通过注入依赖的方式传递,有点类似于reactcontext

  • 父组件
<script setup>
import { countSymbol } from './injectionSymbols'

// 提供静态值
provide('path', '/project/')

// 提供响应式的值
const count = ref(0)
provide('count', count)

// 提供时将 Symbol 作为 key
provide(countSymbol, count)
</script>
  • 子组件就通过inject获取值就行。
<script setup>
import { countSymbol } from './injectionSymbols'

const path = inject('path')

const count = inject('count')

// 当 Symbol 作为 key时特殊一点
const count2 = inject(countSymbol)
</script>

Vuex 或者 Pinia

这个没什么好说的,官方的状态管理库,但是要注意一下使用条件,我个人觉得如果你是大型应用,状态特别多,跨组件传值也特别多的时候再使用,否则确实有点累赘了。

EventBus

今天晚上的重头戏来了,这个问题也是我那次回答的亮点,我能从面试官眼中看到明显的惊讶,显然他没想到如此巧妙的方法。

一般实现

所谓的EventBus,其实就是就一个观察者模式,通过注册与通知来实现组件间的通讯,我随手写一下我自己的实现

class Emitter{
  events = new Map()
  on(name,cb){ //添加事件监听
    if(this.events.has(name)){
      this.events.get(name).add(cb)
    }else{
      const cbs = new Set([cb])
      this.events.set(name,cbs)
    }
  }
  off(name,cb){ //解除事件监听
    if(this.events.has(name)){
      const cbs = this.events.get(name)
      cbs.delete(cb)

      if(cbs.size === 0){
        this.events.delete(name)
    }
  }
  emit(name,data){ // 触发响应事件
    if(this.events.has(name)){
      const cbs = this.events.get(name)
      cbs.forEach(cb=>cb(data))
    }
  }
  clear(){ // 清除全部事件
    this.events.clear()
  }
}

这样就可以实现一个基本的EventBus,使用的时候需要把实例放到全局,如果你还想进一步优化,最好写一个单例,但是如果是面试你能这样说基本上就够了。 But。。。

我的答案

我给面试官回答,原生js都有,干嘛写,写起来还复杂了。我用的正是CustomEvent,代码如下

// 触发事件
const evt = new CustomEvent('name', {detail:'事件携带的data'})
document.body.dispatchEvent(evt)

触发事件的时候注意在哪个具体的DOM上触发,我一边图方便在body上,你也可以在更具体的dom上触发。监听就是咱们常用的addEventListener.

// 监听事件
document.body.addEventListener('name',({detail})=>{console.log(detail)})

这样就实现了上面那么长代码的功能,而且是js原生自带的,兼容性也是非常的棒,不存在任何问题。

当我这样回答完之后,邪魅一笑,面试官大惊失色,竟然说:还能这样!

完结

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