From 13008d1f26b92dbcf9662598d1ffc8bb4ee00c20 Mon Sep 17 00:00:00 2001 From: Muyao Date: Thu, 14 Oct 2021 22:20:50 +0800 Subject: [PATCH] feat(vue): support useParentForm hook (#2306) --- packages/antd/src/form/index.tsx | 6 +- packages/element/src/form/index.ts | 96 +++++++++++-------- packages/element/src/reset/index.ts | 4 +- packages/element/src/submit/index.ts | 4 +- packages/next/src/form/index.tsx | 6 +- packages/vue/docs/.vuepress/config.js | 1 + .../vue/docs/api/hooks/use-parent-form.md | 17 ++++ .../docs/demos/api/hooks/use-parent-form.vue | 51 ++++++++++ packages/vue/src/__tests__/form.spec.ts | 56 +++++++++-- packages/vue/src/hooks/index.ts | 1 + packages/vue/src/hooks/useForm.ts | 3 - packages/vue/src/hooks/useParentForm.ts | 15 +++ 12 files changed, 198 insertions(+), 62 deletions(-) create mode 100644 packages/vue/docs/api/hooks/use-parent-form.md create mode 100644 packages/vue/docs/demos/api/hooks/use-parent-form.vue create mode 100644 packages/vue/src/hooks/useParentForm.ts diff --git a/packages/antd/src/form/index.tsx b/packages/antd/src/form/index.tsx index 9d872a6e36d..d21547a05e3 100644 --- a/packages/antd/src/form/index.tsx +++ b/packages/antd/src/form/index.tsx @@ -20,7 +20,7 @@ export const Form: React.FC = ({ ...props }) => { const top = useForm() - const content = ( + const renderContent = (form: FormType) => ( {React.createElement( @@ -37,9 +37,9 @@ export const Form: React.FC = ({ ) - if (top) return content + if (top) return renderContent(top) if (!form) throw new Error('must pass form instance by createForm') - return {content} + return {renderContent(form)} } Form.defaultProps = { diff --git a/packages/element/src/form/index.ts b/packages/element/src/form/index.ts index c0d82eaf88e..232886cbb2d 100644 --- a/packages/element/src/form/index.ts +++ b/packages/element/src/form/index.ts @@ -1,5 +1,5 @@ import { Form as FormType, IFormFeedback } from '@formily/core' -import { FormProvider as _FormProvider, h } from '@formily/vue' +import { FormProvider as _FormProvider, h, useForm } from '@formily/vue' import { defineComponent } from '@vue/composition-api' import { FormLayout, FormLayoutProps } from '../form-layout' import { PreviewText } from '../preview-text' @@ -8,7 +8,7 @@ import { Component, VNode } from 'vue' const FormProvider = _FormProvider as unknown as Component export interface FormProps extends FormLayoutProps { - form: FormType + form?: FormType component?: Component previewTextPlaceholder: string | (() => VNode) onAutoSubmit?: (values: any) => any @@ -25,6 +25,8 @@ export const Form = defineComponent({ 'onAutoSubmitFailed', ], setup(props, { attrs, slots, listeners }) { + const top = useForm() + return () => { const { form, @@ -33,50 +35,60 @@ export const Form = defineComponent({ onAutoSubmitFailed = listeners?.autoSubmitFailed, previewTextPlaceholder = slots?.previewTextPlaceholder, } = props + + const renderContent = (form: FormType) => { + return h( + PreviewText.Placeholder, + { + props: { + value: previewTextPlaceholder, + }, + }, + { + default: () => [ + h( + FormLayout, + { + attrs: { + ...attrs, + }, + }, + { + default: () => [ + h( + component, + { + on: { + submit: (e: Event) => { + e?.stopPropagation?.() + e?.preventDefault?.() + form + .submit(onAutoSubmit as (e: any) => void) + .catch(onAutoSubmitFailed as (e: any) => void) + }, + }, + }, + slots + ), + ], + } + ), + ], + } + ) + } + + if (top.value) { + return renderContent(top.value) + } + + if (!form) throw new Error('must pass form instance by createForm') + return h( FormProvider, { props: { form } }, { - default: () => - h( - PreviewText.Placeholder, - { - props: { - value: previewTextPlaceholder, - }, - }, - { - default: () => [ - h( - FormLayout, - { - attrs: { - ...attrs, - }, - }, - { - default: () => [ - h( - component, - { - on: { - submit: (e: Event) => { - e?.stopPropagation?.() - e?.preventDefault?.() - form - .submit(onAutoSubmit as (e: any) => void) - .catch(onAutoSubmitFailed as (e: any) => void) - }, - }, - }, - slots - ), - ], - } - ), - ], - } - ), + default: () => renderContent(form), } ) } diff --git a/packages/element/src/reset/index.ts b/packages/element/src/reset/index.ts index 27d89036e98..2ef222bbc8b 100644 --- a/packages/element/src/reset/index.ts +++ b/packages/element/src/reset/index.ts @@ -1,5 +1,5 @@ import { IFieldResetOptions } from '@formily/core' -import { h, useForm } from '@formily/vue' +import { h, useParentForm } from '@formily/vue' import { observer } from '@formily/reactive-vue' import { defineComponent } from '@vue/composition-api' @@ -22,7 +22,7 @@ export const Reset = observer( }, }, setup(props, context) { - const formRef = useForm() + const formRef = useParentForm() const { listeners, slots } = context return () => { const form = formRef?.value diff --git a/packages/element/src/submit/index.ts b/packages/element/src/submit/index.ts index 3fdd532cf62..769733faf14 100644 --- a/packages/element/src/submit/index.ts +++ b/packages/element/src/submit/index.ts @@ -1,4 +1,4 @@ -import { h, useForm } from '@formily/vue' +import { h, useParentForm } from '@formily/vue' import { IFormFeedback } from '@formily/core' import { observer } from '@formily/reactive-vue' import { defineComponent } from '@vue/composition-api' @@ -18,7 +18,7 @@ export const Submit = observer( name: 'FSubmit', props: ['onClick', 'onSubmit', 'onSubmitSuccess', 'onSubmitFailed'], setup(props, { attrs, slots, listeners }) { - const formRef = useForm() + const formRef = useParentForm() return () => { const { diff --git a/packages/next/src/form/index.tsx b/packages/next/src/form/index.tsx index 5fd6684bc6e..279c070ef3a 100644 --- a/packages/next/src/form/index.tsx +++ b/packages/next/src/form/index.tsx @@ -33,7 +33,7 @@ export const Form: React.FC = ({ setValidateLanguage(validateLanguage) }, [lang]) - const content = ( + const renderContent = (form: FormType) => ( {React.createElement( @@ -51,11 +51,11 @@ export const Form: React.FC = ({ ) - if (top) return content + if (top) return renderContent(top) if (!form) throw new Error('must pass form instance by createForm') - return {content} + return {renderContent(form)} } Form.defaultProps = { diff --git a/packages/vue/docs/.vuepress/config.js b/packages/vue/docs/.vuepress/config.js index b38e9650e9b..71045e9bfe3 100644 --- a/packages/vue/docs/.vuepress/config.js +++ b/packages/vue/docs/.vuepress/config.js @@ -62,6 +62,7 @@ module.exports = { '/api/hooks/use-field-schema', '/api/hooks/use-form', '/api/hooks/use-form-effects', + '/api/hooks/use-parent-form', ], }, { diff --git a/packages/vue/docs/api/hooks/use-parent-form.md b/packages/vue/docs/api/hooks/use-parent-form.md new file mode 100644 index 00000000000..3a326e09577 --- /dev/null +++ b/packages/vue/docs/api/hooks/use-parent-form.md @@ -0,0 +1,17 @@ +# useParentForm + +## 描述 + +用于读取最近的 Form 或者 ObjectField 实例,主要方便于调用子表单的 submit/validate + +## 签名 + +```ts +interface useParentForm { + (): Form | ObjectField +} +``` + +## 用例 + + diff --git a/packages/vue/docs/demos/api/hooks/use-parent-form.vue b/packages/vue/docs/demos/api/hooks/use-parent-form.vue new file mode 100644 index 00000000000..562e9070201 --- /dev/null +++ b/packages/vue/docs/demos/api/hooks/use-parent-form.vue @@ -0,0 +1,51 @@ + + + diff --git a/packages/vue/src/__tests__/form.spec.ts b/packages/vue/src/__tests__/form.spec.ts index b33ce062e19..1857e7e9f3b 100644 --- a/packages/vue/src/__tests__/form.spec.ts +++ b/packages/vue/src/__tests__/form.spec.ts @@ -1,10 +1,22 @@ import Vue from 'vue' import { render } from '@testing-library/vue' import { createForm } from '@formily/core' -import { FormProvider, FormConsumer } from '../vue2-components' +import { + FormProvider, + FormConsumer, + Field, + ObjectField, + VoidField, +} from '../vue2-components' +import { defineComponent } from 'vue-demi' +import { useParentForm } from '../hooks' +import { h } from 'vue-demi' Vue.component('FormProvider', FormProvider) Vue.component('FormConsumer', FormConsumer) +Vue.component('ObjectField', ObjectField) +Vue.component('VoidField', VoidField) +Vue.component('Field', Field) test('render form', () => { const form = createForm() @@ -22,11 +34,41 @@ test('render form', () => { `, }) - const errorRender = () => - render({ - template: ``, - }) - expect(form.mounted).toBeTruthy() - expect(errorRender).toThrow('Can not found form instance from context.') +}) + +const DisplayParentForm = defineComponent({ + setup() { + const form = useParentForm() + + return () => h('div', [form.value.displayName]) + }, +}) + +test('useParentForm', () => { + const { queryByTestId } = render({ + components: { + DisplayParentForm, + }, + data() { + const form = createForm() + return { form } + }, + template: ` + + + + + + + + + + + + `, + }) + expect(queryByTestId('111').textContent).toBe('ObjectField') + expect(queryByTestId('222').textContent).toBe('Form') + expect(queryByTestId('333').textContent).toBe('Form') }) diff --git a/packages/vue/src/hooks/index.ts b/packages/vue/src/hooks/index.ts index d0ca291aefb..270be213df2 100644 --- a/packages/vue/src/hooks/index.ts +++ b/packages/vue/src/hooks/index.ts @@ -2,3 +2,4 @@ export * from './useForm' export * from './useField' export * from './useFormEffects' export * from './useFieldSchema' +export * from './useParentForm' diff --git a/packages/vue/src/hooks/useForm.ts b/packages/vue/src/hooks/useForm.ts index 11c7981461a..9ccbd8c454e 100644 --- a/packages/vue/src/hooks/useForm.ts +++ b/packages/vue/src/hooks/useForm.ts @@ -4,8 +4,5 @@ import { FormSymbol } from '../shared/context' export const useForm = (): Ref
=> { const form = inject(FormSymbol, ref()) - if (!form.value) { - throw new Error('Can not found form instance from context.') - } return form } diff --git a/packages/vue/src/hooks/useParentForm.ts b/packages/vue/src/hooks/useParentForm.ts new file mode 100644 index 00000000000..24377e657e8 --- /dev/null +++ b/packages/vue/src/hooks/useParentForm.ts @@ -0,0 +1,15 @@ +import { isObjectField, GeneralField, Form, ObjectField } from '@formily/core' +import { computed, Ref } from 'vue-demi' +import { useField } from './useField' +import { useForm } from './useForm' + +export const useParentForm = (): Ref => { + const field = useField() + const form = useForm() + const findObjectParent = (field: GeneralField) => { + if (!field) return form.value + if (isObjectField(field)) return field + return findObjectParent(field?.parent) + } + return computed(() => findObjectParent(field.value)) +}