Skip to content

Commit

Permalink
feat(vue): support useParentForm hook (#2306)
Browse files Browse the repository at this point in the history
  • Loading branch information
muuyao authored Oct 14, 2021
1 parent c2c2e30 commit 13008d1
Show file tree
Hide file tree
Showing 12 changed files with 198 additions and 62 deletions.
6 changes: 3 additions & 3 deletions packages/antd/src/form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const Form: React.FC<FormProps> = ({
...props
}) => {
const top = useForm()
const content = (
const renderContent = (form: FormType) => (
<PreviewText.Placeholder value={previewTextPlaceholder}>
<FormLayout {...props}>
{React.createElement(
Expand All @@ -37,9 +37,9 @@ export const Form: React.FC<FormProps> = ({
</FormLayout>
</PreviewText.Placeholder>
)
if (top) return content
if (top) return renderContent(top)
if (!form) throw new Error('must pass form instance by createForm')
return <FormProvider form={form}>{content}</FormProvider>
return <FormProvider form={form}>{renderContent(form)}</FormProvider>
}

Form.defaultProps = {
Expand Down
96 changes: 54 additions & 42 deletions packages/element/src/form/index.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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
Expand All @@ -25,6 +25,8 @@ export const Form = defineComponent<FormProps>({
'onAutoSubmitFailed',
],
setup(props, { attrs, slots, listeners }) {
const top = useForm()

return () => {
const {
form,
Expand All @@ -33,50 +35,60 @@ export const Form = defineComponent<FormProps>({
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),
}
)
}
Expand Down
4 changes: 2 additions & 2 deletions packages/element/src/reset/index.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions packages/element/src/submit/index.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions packages/next/src/form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const Form: React.FC<FormProps> = ({
setValidateLanguage(validateLanguage)
}, [lang])

const content = (
const renderContent = (form: FormType) => (
<PreviewText.Placeholder value={previewTextPlaceholder}>
<FormLayout {...props}>
{React.createElement(
Expand All @@ -51,11 +51,11 @@ export const Form: React.FC<FormProps> = ({
</PreviewText.Placeholder>
)

if (top) return content
if (top) return renderContent(top)

if (!form) throw new Error('must pass form instance by createForm')

return <FormProvider form={form}>{content}</FormProvider>
return <FormProvider form={form}>{renderContent(form)}</FormProvider>
}

Form.defaultProps = {
Expand Down
1 change: 1 addition & 0 deletions packages/vue/docs/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
],
},
{
Expand Down
17 changes: 17 additions & 0 deletions packages/vue/docs/api/hooks/use-parent-form.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# useParentForm

## 描述

用于读取最近的 Form 或者 ObjectField 实例,主要方便于调用子表单的 submit/validate

## 签名

```ts
interface useParentForm {
(): Form | ObjectField
}
```

## 用例

<dumi-previewer demoPath="api/hooks/use-parent-form" />
51 changes: 51 additions & 0 deletions packages/vue/docs/demos/api/hooks/use-parent-form.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<template>
<FormProvider :form="form">
<ObjectField name="object">
<Custom></Custom>
</ObjectField>
<Custom></Custom>
<VoidField name="void">
<Custom></Custom>
</VoidField>
</FormProvider>
</template>

<script>
import { defineComponent, h } from '@vue/composition-api'
import { createForm } from '@formily/core'
import {
FormProvider,
ObjectField,
VoidField,
useParentForm,
} from '@formily/vue'
import { observer } from '@formily/reactive-vue'
const Custom = observer(
defineComponent({
setup() {
const formRef = useParentForm()
return () => {
const form = formRef.value
return h('div', {}, [form.displayName])
}
},
})
)
export default {
components: {
FormProvider,
ObjectField,
VoidField,
VoidField,
Custom,
},
data() {
const form = createForm()
return {
form,
}
},
}
</script>
56 changes: 49 additions & 7 deletions packages/vue/src/__tests__/form.spec.ts
Original file line number Diff line number Diff line change
@@ -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()
Expand All @@ -22,11 +34,41 @@ test('render form', () => {
</FormProvider>`,
})

const errorRender = () =>
render({
template: `<FormConsumer />`,
})

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: `<FormProvider :form="form">
<ObjectField name="aa">
<Field name="bb">
<DisplayParentForm data-testid="111" />
</Field>
</ObjectField>
<VoidField name="cc">
<Field name="dd">
<DisplayParentForm data-testid="222" />
</Field>
</VoidField>
<DisplayParentForm data-testid="333" />
</FormProvider>`,
})
expect(queryByTestId('111').textContent).toBe('ObjectField')
expect(queryByTestId('222').textContent).toBe('Form')
expect(queryByTestId('333').textContent).toBe('Form')
})
1 change: 1 addition & 0 deletions packages/vue/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './useForm'
export * from './useField'
export * from './useFormEffects'
export * from './useFieldSchema'
export * from './useParentForm'
3 changes: 0 additions & 3 deletions packages/vue/src/hooks/useForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,5 @@ import { FormSymbol } from '../shared/context'

export const useForm = (): Ref<Form> => {
const form = inject(FormSymbol, ref())
if (!form.value) {
throw new Error('Can not found form instance from context.')
}
return form
}
15 changes: 15 additions & 0 deletions packages/vue/src/hooks/useParentForm.ts
Original file line number Diff line number Diff line change
@@ -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<Form | ObjectField> => {
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))
}

0 comments on commit 13008d1

Please sign in to comment.