Vue3 相当于 vNode.computedInstance

Vue3 equivalent of vNode.computedInstance

我正在将我的应用程序从 Vue2 转换为 Vue3,但在我的表单的一个方面停滞不前。

我将 SFC 用于表单元素组件(FormInput、FormTextArea、FormCheckbox 等),并使用槽将它们传递到表单容器组件(FormGroup、FormTab 等)中,如下所示:

<ssi-form>
  <ssi-form-tabs>
    <ssi-form-tab title="tab1">
      <ssi-form-input title="name" ... />
      <ssi-form-checkbox title="active" ... />
    </ssi-form-tab>
  </ssi-form-tabs>
</ssi-form>

那些父容器需要查看子表单元素的一些计算属性以将错误消息拉到顶部。

在 Vue2 中,我使用挂载的生命周期钩子(带有选项 API)来读取插槽并访问计算属性,如下所示:

  mounted: function() {
    const vm = this;

    this.$slots.default.forEach((vNode) => {
      const vueComponent = vNode.componentInstance;
      vueComponent.$on("invalid", vm.onInvalid);

      if (vueComponent.errorCount === undefined) return;
      this.childFormElements.push(vueComponent);
    });
  },

使用此设置,我可以从插槽中的每个子项中获取 errorCount 计算的 属性,因此我可以将错误汇总到表单的顶层。

现在我切换到 Vue3,componentInstance 似乎不存在。我尝试使用 onMounted 指令设置类似的逻辑,但是当我访问槽元素时,我找不到任何方法来查看它们的 errorCount 计算 属性:

onMounted(() => { 
  slots.default().forEach((vNode) => {
    console.log(vNode);
  });
});

记录的对象不包含计算的 属性。当我读到 defineExpose 时,我以为我发现了一些有用的东西,但即使在暴露 errorCount 属性 之后,也没有任何结果。

这是我尝试使用的来自 SFC 的文本输入的 <script setup>

<script setup lang="ts">
import { ref, defineProps, defineEmits, computed } from "vue";

let props = defineProps<{
  label: string, 
  id: string, 
  modelValue: string|number, 
  type?: string, 
  description?: string, 
  min?: string|number, 
  max?: string|number, 
  pattern?: string, 
  message?: string
}>();
let emit = defineEmits(["input", "update:modelValue", "invalid"]);

let state = ref(null);
let error = ref("");

const input = ref(null);

function onInput(event: Event) {
  validate();
  emit('update:modelValue', event.target.value)
}

// methods
function validate() {
  let inputText = input.value;
  if (inputText === null) return;

  inputText.checkValidity();
  state.value = inputText.validity.valid;
  error.value = inputText.validationMessage;
}

const errorCount = computed(() => {
  return state.value === false ? 1 : 0;
});

defineExpose({errorCount})

</script>

所以问题是 - 父组件如何从放置在插槽中的组件读取 errorCount 属性?

我认为这是不可行的,因为它已经被认为是一种 不好的做法 since Vue 2.

defineExpose 在这种情况下确实有帮助,但既然你说

I could grab the errorCount computed property from each child in the slot

也许 Provide / Inject 是更好的方法?

内部属性不应在没有充分理由的情况下使用,尤其是因为它们不属于 public API 并且可以更改其行为或被删除,恕不另行通知。一个更好的解决方案是可以通过 public API.

实现目标的解决方案

这里的解决方法是process slots容器组件的render函数。 Vnode 对象是渲染元素的模板,它们可以在此时进行转换,例如可以添加来自容器组件范围的引用。

如果需要访问子组件的实例以添加事件侦听器,此时它们 can be added 到 vnode:

() => {
  const tabChildren = slots.default?.() || [];
  
  for (childVnode of tabChildren) {
    // Specifically check against a list of components that need special treatment
    if (childVnode.type === SsiFormInputComponent) {
      childVnode.props = {
        ...childVnode.props,
        ref: setTabChildRef,
        onInvalid: invalidHandler,
      };
    }
  }

  return tabChildren;
}

其中 setTabChildRefref function 维护子 ref 集合的地方,尽管它并不是仅出于添加事件侦听器的目的而需要的。