问题描述
我目前正在进行自定义验证,并且希望在可能的情况下访问子组件并在其中调用方法。
表格包装器
<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。
如果有人可以帮助我,那太好了,或者甚至有可能做到。
解决方法
在您提供的链接中,Linus提到使用$on
和$off
来做到这一点。这些已在Vue 3中删除,但是您可以使用recommended mitt
库。
一种方法是向子组件调度submit
事件,并在子组件收到validate
时发出submit
事件。但是也许您无权将其添加到子组件中?
<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');
,
我发现了另一种受类星体框架启发的解决方案。
- 表单组件的provide()绑定和取消绑定功能。
bind()将validate函数推入数组并存储在Form组件中。 - 输入组件从父Form组件注入bind和unbind函数。
使用self validate()函数和uid运行bind() - 通过“提交”按钮进行表单监听提交事件。
运行所有这些validate()数组,如果没有问题,则发出('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>