关于使用provide/inject结合ref和watch实现父子组件间通讯

来源:5-10 ValidateForm 编码第三部分 - 寻找外援 mitt

陈加州

2022-09-03

用RT的方法实现了点击提交按钮执行ValidateInput组件上所有的验证方法,结果暂时跑通了,但是不知道这样操作有没有什么潜在问题,想问一下老师。另外可以的话,希望老师能简单比较一下这个方法和使用第三方库的实现事件总线间的利弊吗?

我首先是在读v3-migration文档的时候看到这页最下面有这么一段专门讲Event Bus的,整体比较长,摘出来一段。

Event Bus

  • Provide / inject allow a component to communicate with its slot contents. This is useful for tightly-coupled components that are always used together.
  • Provide / inject can also be used for long-distance communication between components. It can help to avoid ‘prop drilling’, where props need to be passed down through many levels of components that don’t need those props themselves.

听了课上使用外部包来实现父组件调用子组件slot内容里的函数之后,总感觉有点不舒服,就在想有没有别的办法。然后读到讲上面链接里讲Event Bus里的地方,也说一般情况下不推荐使用事件总线实现组件间通讯。同时下面给出了几个代替方案,其中有提到了provide/inject,而且这里的form组件input组件符合上面第一条描述的情况,是"tightly-coupled"的。

于是动手试了下,想法是在form组件provide一个Ref,input组件里获取这个Ref后, 使用watch函数监听这个Ref的变化,当有变化时就运行input组件上的验证函数。最后在form组件里定义onSubmit函数,每次有提交时就拨动一下Ref。 等于利用watch函数,让所有input组件成为父form组件submit事件的subscribers。这次的”点击提交表单同时验证所有Input框的内容“只是调用一个特定的函数,如果在这个watch函数里使用switch/case的话应该可以实现在父组件上改变Ref的值来调用子组件上各种各样的函数,感觉这个写法是有扩展性的。不知道这样写会不会有什么问题?


更新: 看到第12课里有跑完所有input组件上的验证函授后给出表单整体验证结果的需求,provide一个值不够,再provide另外一个值formResult,由于form组件需要等所有子组件上的函数跑完验证再确认验证结果,而拨动validateControl这个值之后input组件验证函数调用是非同步的,加了个await nextTick()来强制同步,开始觉得还是用emitter好了[Facepalm]


代码大致是这样的感觉:

/*****  ValidateForm.vue  *****/ 
// <template>
<div @click.prevent="onSubmit">
  <slot name="submit">
    <button type="submit">提交</button>
  </slot>
</div>
  
//<script>
setup() {
  const validateControl: Ref<number> = ref(1)
  const formResult: Ref<boolean> = ref(true)
  
  provide('validateControl', validateControl)
  provide('formResult', formResult)

  const onSubmit = () => {
    validateControl.value *= -1 // trigger watch on child input component
    // child input component subscribers run validation on their own...
    await nextTick() 
    // doOtherBusinessLogic()
    
    // reset form validation result	
    formResult.value = true
  }
}

/***** ValidateInput.vue *****/
setup() {
  const validateControl = inject('validateControl') as Ref<number>
  const formResult = inject('formResult') as Ref<number>
  watch(validateControl, (oldval:Ref<number>, newVal:Ref<number>) => {
    // Run all validation rules defined on this ValidateInput component, set the error msg if needed, and return the validation result for current input value
    inputRef.valid = validate() 
    // if any one of the input components is invalid, then the whole form is invalid
    formResult.value = formResult.value && inputRef.valid 
  })
}
写回答

1回答

张轩

2022-09-04

同学你好

非常感谢你的回复,说明你思考了很多。很厉害

我认为你的方案是完全可行的,使用 Provide/Inject 的原理其实和使用 mitt 一样,都是通过事件订阅机制完成对应的需求,我这个方案是参考了旧版的 element/ant-design 的方式,它们都是新建了一个这样的对象,或者称之为订阅器。

新版的 element-plus 在 Form 表单的实现上使用了Provide/Inject 机制,如果有能力,可以简单看下源代码:https://github.com/element-plus/element-plus/blob/dev/packages/components/form/src/form.vue#L159

总之:给你的学习和思考能力点个赞,你也可以按照 Provide/Inject 的方式进行重写,未来我们也可以更新使用类似的方式。

3
0

Vue3 + TS 仿知乎专栏企业级项目

带你完成前后端分离复杂项目,率先掌握 vue3 造轮子技能

3142 学习 · 2313 问题

查看课程