Vue 3 从插槽访问子组件

Posted

技术标签:

【中文标题】Vue 3 从插槽访问子组件【英文标题】:Vue 3 access child component from slots 【发布时间】:2021-02-01 01:56:51 【问题描述】:

我目前正在进行自定义验证,如果可能,我希望访问子组件并在其中调用一个方法。

表单包装器

<template>
    <form @submit.prevent="handleSubmit">
        <slot></slot>
    </form>
</template>

<script lang="ts">
import  defineComponent  from 'vue';

export default defineComponent(
    setup(props,  slots ) 
        const validate = (): boolean => 
            if (slots.default) 
                slots.default().forEach((vNode) => 
                    if (vNode.props && vNode.props.rules) 
                        if (vNode.component) 
                            vNode.component.emit('validate');
                        
                    
                );
            

            return false;
        ;

        const handleSubmit = (ev: any): void => 
            validate();
        ;

        return 
            handleSubmit,
        ;
    ,
);
</script>

当我调用slot.default() 时,我会得到正确的子组件列表并且可以看到它们的道具。但是,vNode.component 始终是 null

我的代码基于example,但它适用于 vue 2。

如果有人可以帮助我,那就太好了,或者这甚至可能做到。

【问题讨论】:

【参考方案1】:

在您提供的链接中,Linus 提到使用 $on$off 来执行此操作。这些已在 Vue 3 中删除,但您可以使用 recommended mitt 库。

一种方法是将submit 事件分派给子组件,并让它们在收到submit 时发出validate 事件。但也许您无权将此添加到子组件?

JSFiddle Example

<div id="app">
    <form-component>
        <one></one>
        <two></two>
        <three></three>
    </form-component>
</div>
const emitter = mitt();

const ChildComponent = 
    setup(props,  emit ) 
        emitter.on('submit', () =>   
            console.log('Child submit event handler!');

            if (props && props.rules) 
                emit('validate');
            
        );
    ,
;

function makeChild(name) 
    return 
        ...ChildComponent,
        template: `<input value="$name" />`,
    ;


const formComponent = 
    template: `
        <form @submit.prevent="handleSubmit">
            <slot></slot>
            <button type="submit">Submit</button>
        </form>
    `,

    setup() 
        const handleSubmit = () => emitter.emit('submit');
        return  handleSubmit ;
    ,
;

const app = Vue.createApp(
    components: 
        formComponent,
        one: makeChild('one'),
        two: makeChild('two'),
        three: makeChild('three'),
    
);

app.mount('#app');

【讨论】:

【参考方案2】:

我找到了另一个解决方案,灵感来自 quasar 框架。

    表单组件提供()绑定和取消绑定功能。 bind() 将验证函数推送到数组并存储在 Form 组件中。 输入组件从父表单组件注入绑定和取消绑定功能。 使用 self validate() 函数和 uid 运行 bind() 表单从提交按钮监听提交事件。 遍历所有这些 validate() 数组,如果没有问题则 emit('submit')

表单组件

import 
  defineComponent,
  onBeforeUnmount,
  onMounted,
  reactive,
  toRefs,
  provide
 from "vue";
export default defineComponent(
  name: "Form",
  emits: ["submit"],
  setup(props,  emit ) 
    const state = reactive(
      validateComponents: []
    );
    provide("form", 
      bind,
      unbind
    );
    onMounted(() => 
      state.form.addEventListener("submit", onSubmit);
    );
    onBeforeUnmount(() => 
      state.form.removeEventListener("submit", onSubmit);
    );
    function bind(component) 
      state.validateComponents.push(component);
    
    function unbind(uid) 
      const index = state.validateComponents.findIndex(c => c.uid === uid);
      if (index > -1) 
        state.validateComponents.splice(index, 1);
      
    
    function validate() 
      let valid = true;
      for (const component of state.validateComponents) 
        const result = component.validate();
        if (!result) 
          valid = false;
        
      
      return valid;
    
    function onSubmit() 
      const valid = validate();
      if (valid) 
        emit("submit");
      
    
  
);

输入组件

import  defineComponent  from "vue";
export default defineComponent(
  name: "Input",
  props: 
    rules: 
      default: () => [],
      type: Array
    ,
    modelValue: 
      default: null,
      type: String
    
  
  setup(props) 
    const form = inject("form");
    const uid = getCurrentInstance().uid;
    onMounted(() => 
      form.bind( validate, uid );
    );
    onBeforeUnmount(() => 
      form.unbind(uid);
    );
    function validate() 
      // validate logic here
      let result = true;
      props.rules.forEach(rule => 
        const value = rule(props.modelValue);
        if(!value) result = value;
      )
      return result;
    
  
);

用法

<template>
  <form @submit="onSubmit">
    <!-- rules function -->
    <input :rules="[(v) => true]">
    <button label="submit form" type="submit">
  </form>
</template>

【讨论】:

以上是关于Vue 3 从插槽访问子组件的主要内容,如果未能解决你的问题,请参考以下文章

Vue第四天学习笔记之组件化高级

Vue 3 - 从插槽内的子组件发出事件

从插槽模板中访问当前组件数据

快速理解Vue组件化开发,爆肝半万字,干货太多,”建议收藏细品“

从槽内访问父组件的方法

来自子组件的 Vue 3 插槽样式