likes
comments
collection
share

分享Vue3组件库搭建(3):子父组件是如何通信的,Radio与RadioGroup,Tab与TabItem等实例

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

前言

每当问起vue的通信方式,常常有人解释最不清晰的,就是provideinject。有人提到写组件库的时候才会用到,具体怎么用,再深入就不太了解。这就是本文的重点,也是vue组件库的通信神器,无论vue2还是vue3,都给组件库的带来的偌大的方便。

分享Vue3组件库搭建(3):子父组件是如何通信的,Radio与RadioGroup,Tab与TabItem等实例

本文重点,组件的通信,不仅仅是provide与inject。如vue2还需结合dispatchbroadcast等来完成子父通信,本文的vue3也需要结合Emitter等来辅助。我们逐步分析。

需求分析

vue2子父通信,想了解可以参考iview: github.com/iview/iview…

本章重点vue3,我们先分析一下组件库的通信需要通信些什么,这里我们用Radio与RadioGroup的来举例。

直接看图例:

分享Vue3组件库搭建(3):子父组件是如何通信的,Radio与RadioGroup,Tab与TabItem等实例

实现重点

步骤1:搭建Emitter数据中心

根据图例,我们需要搭建一个Emitter事件总程,来记录所有的子父组件绑定的key。

思路也很简单,就是每个父组件注册一个唯一标识key,子组件找到这个key就可以绑定关系。

import { reactive, ref } from "vue"

export class Emitter {
  // 响应式的数据中心
  private state = reactive({})
  private events: any = ref({})

  // 用于注册事件
  on(eventName: string, eventHandle: Function) {
    this.events[eventName] = eventHandle
    // console.log(`this.event.on====`, this.events)
  }

  // 删除事件
  off(eventName: string) {
    if (this.events[eventName]) {
      delete this.events[eventName]
    }
  }

  // 触发事件
  emit(eventName: string, ...rest: any[]) {
    // console.log(`this.event.emit====`, this.events)
    if (this.events[eventName]) {
      this.events[eventName](...rest)
    }
  }
}

而父组件注册一个唯一标识key也很简单:

export const emitterKey = Symbol()

步骤2:利用provide与inject绑定父子关系

步骤1父组件生成了emitterKey,那么子组件如何找到了。这里的子组件,不单单是指的是children, 可能是children的children的children。所以,此时我们的provide与inject派上用场。

首先,父组件provide这个Key值:

const emitter = new Emitter()
provide(emitterKey, emitter, acceptRadioGroup, provide)

子组件需要认这个父组件, 首先找到父组件

import { Emitter } from "../common/emitter"
import { emitterKey } from "./radio-group/radio-group.vue"

const emitterInject = inject<Emitter>(emitterKey, null)

此时,如果子组件对应的父组件不为空,emitterInject已经可以拿到父组件暴露的方法。我们使用他绑定关系:

父组件,我们新建一个数组来储存子组件的每个方法:

const stateComp = reactive({
  groupValue: props.value,
  updateRadioStatusFnList: [] as any[],
})

emitter.on("acceptRadioFn", (updateFunc: any) => {
  stateComp.updateRadioStatusFnList.push(updateFunc)
})

子组件:

  emitterInject && emitterInject.emit("acceptRadioFn", stateComp.parentUpdate)

此时,子组件的updateRadioStatusFnList,记录了所有的子组件更新方法。

步骤3:父组件触发所有子组件变化

上述已经记录了updateRadioStatusFnList。此时如果父组件想修改所有子组件的变化,也很简单:

//通知对应所有的编辑状态关闭
    stateComp.updateRadioStatusFnList.map(fn => {
      fn(stateComp.groupValue)
    })
    

这样,步骤2记录的所有方法,父组件就可以一步完成。

走到这里,我们就可以完成常用的一些场景。如:

radioGroup修改成“不可编辑”时,通知所有的radio不可点击。

步骤4:子组件变化,通知所有的同类组件变化

那么此时,一个radio给选中了,我要告诉radioGroup下其他的同类radio,都变成不可选中。如何处理?

首先,所有的子组件,重复2~3步骤,需注册到父级监听中:

const acceptRadioGroup = inject<Function>("acceptRadioGroup", null)
const stateComp = reactive({
  groupValue: inject("xRadioGroupValue"),
  select: () => {
    acceptRadioGroup(props.name)
  },
  parentUpdate: (value: any) => {
    if (stateComp.groupValue !== value) {
      stateComp.groupValue = value
    }
  }
})

父组件:

const acceptRadioGroup = (name: string) => {
  stateComp.groupValue = name
  emit("update:value", name)
  stateComp.setVisableList() //同步所有的子选项
}

结语

此时,radioGroup与radio的关系,已经可以达到互相通知:

    1. radioGroup知道自己所有的radio子组件
    1. radioGroup可以同时修改自己的所有的radio子组件
    1. 任意一个radio, 可以触发radioGroup,修改所有子组件的。

理解完成这里的通信,我们的tab与tabItem实例,form与formItem实例, 相信都不是问题。

源码链接

Radio与RadioGroup,Tab与TabItem源码已上传,Form期待下一篇

github: github.com/zhuangweizh…

预览:zhuangweizhan.github.io/cb-ui/dist/…