- {{JSON.stringify(form.values, null, 2)}}
+
+ {{ JSON.stringify(form.values, null, 2) }}
+
@@ -29,34 +33,35 @@
import { defineComponent, h } from '@vue/composition-api'
import { Form, Input, Button } from 'ant-design-vue'
import { createForm, setValidateLanguage } from '@formily/core'
-import {
- FormProvider,
- FormConsumer,
- Field,
- useField,
- observer
-} from '@formily/vue'
+import { FormProvider, FormConsumer, Field, useField } from '@formily/vue'
+import { observer } from '@formily/reactive-vue'
import 'ant-design-vue/dist/antd.css'
setValidateLanguage('en')
-const FormItem = observer(defineComponent({
- setup (props, { slots }) {
- const fieldRef = useField()
- return () => {
- const field = fieldRef.value
- return h(Form.Item, {
- props: {
- label: field.title,
- required: field.required,
- help: field.errors?.length ? field.errors : undefined,
- extra: field.description,
- validateStatus: field.validateStatus,
- }
- }, slots?.default())
- }
- }
-}))
+const FormItem = observer(
+ defineComponent({
+ setup(props, { slots }) {
+ const fieldRef = useField()
+ return () => {
+ const field = fieldRef.value
+ return h(
+ Form.Item,
+ {
+ props: {
+ label: field.title,
+ required: field.required,
+ help: field.errors?.length ? field.errors : undefined,
+ extra: field.description,
+ validateStatus: field.validateStatus,
+ },
+ },
+ slots?.default()
+ )
+ }
+ },
+ })
+)
export default {
components: {
@@ -64,20 +69,20 @@ export default {
FormConsumer,
Field,
Form,
- Button
+ Button,
},
data() {
const form = createForm({ validateFirst: true })
return {
FormItem,
Input,
- form
+ form,
}
},
methods: {
- log (...args) {
+ log(...args) {
console.log(...args)
- }
- }
+ },
+ },
}
diff --git a/packages/vue/docs/demos/api/hooks/use-form.vue b/packages/vue/docs/demos/api/hooks/use-form.vue
index f0396d96014..6f4625ba6d1 100644
--- a/packages/vue/docs/demos/api/hooks/use-form.vue
+++ b/packages/vue/docs/demos/api/hooks/use-form.vue
@@ -10,33 +10,36 @@
diff --git a/packages/vue/src/__tests__/field.spec.ts b/packages/vue/src/__tests__/field.spec.ts
index e3dd7eb7346..fa862937512 100644
--- a/packages/vue/src/__tests__/field.spec.ts
+++ b/packages/vue/src/__tests__/field.spec.ts
@@ -63,7 +63,7 @@ test('render field', async () => {
const atBlur = jest.fn()
const atFocus = jest.fn()
- const { getByTestId, queryByTestId, unmount } = render(
+ const { getByTestId, queryByTestId } = render(
defineComponent({
name: 'TestComponent',
setup() {
@@ -140,7 +140,6 @@ test('render field', async () => {
expect(queryByTestId('ee')).toBeNull()
expect(form.query('aa').get('value')).toEqual('123')
expect(form.query('kk').get('value')).toEqual('123')
- Vue.nextTick(() => unmount)
})
test('ReactiveField', () => {
@@ -155,25 +154,55 @@ test('ReactiveField', () => {
})
test('useAttch', async () => {
- const form = createForm()
+ const form1 = createForm()
const MyComponent = defineComponent({
- props: ['name'],
+ props: ['form', 'name1', 'name2', 'name3', 'name4'],
data() {
- return { form, Input, Decorator }
+ return { Input, Decorator }
},
template: `
-
+
+
+
+
`,
})
const { updateProps } = render(MyComponent, {
props: {
- name: 'aa',
+ form: form1,
+ name1: 'aa',
+ name2: 'bb',
+ name3: 'cc',
+ name4: 'dd',
},
})
- expect(form.query('aa').take().mounted).toBeTruthy()
- await updateProps({ name: 'bb' })
- expect(form.query('aa').take().mounted).toBeFalsy()
- expect(form.query('bb').take().mounted).toBeTruthy()
+ expect(form1.mounted).toBeTruthy()
+ expect(form1.query('aa').take().mounted).toBeTruthy()
+ expect(form1.query('bb').take().mounted).toBeTruthy()
+ expect(form1.query('cc').take().mounted).toBeTruthy()
+ expect(form1.query('dd').take().mounted).toBeTruthy()
+ await updateProps({
+ name1: 'aaa',
+ name2: 'bbb',
+ name3: 'ccc',
+ name4: 'ddd',
+ })
+ await Vue.nextTick()
+ expect(form1.query('aa').take().mounted).toBeFalsy()
+ expect(form1.query('bb').take().mounted).toBeFalsy()
+ expect(form1.query('cc').take().mounted).toBeFalsy()
+ expect(form1.query('dd').take().mounted).toBeFalsy()
+ expect(form1.query('aaa').take().mounted).toBeTruthy()
+ expect(form1.query('bbb').take().mounted).toBeTruthy()
+ expect(form1.query('ccc').take().mounted).toBeTruthy()
+ expect(form1.query('ddd').take().mounted).toBeTruthy()
+ const form2 = createForm()
+ await updateProps({
+ form: form2,
+ })
+ await Vue.nextTick()
+ expect(form1.unmounted).toBeTruthy()
+ expect(form2.mounted).toBeTruthy()
})
test('useFormEffects', async () => {
diff --git a/packages/vue/src/components/ArrayField.ts b/packages/vue/src/components/ArrayField.ts
index 702da60e700..b900ea11019 100644
--- a/packages/vue/src/components/ArrayField.ts
+++ b/packages/vue/src/components/ArrayField.ts
@@ -1,4 +1,4 @@
-import { provide, defineComponent } from 'vue-demi'
+import { provide, defineComponent, watch, computed } from 'vue-demi'
import { useField, useForm } from '../hooks'
import { useAttach } from '../hooks/useAttach'
import ReactiveField from './ReactiveField'
@@ -64,18 +64,27 @@ export default observer(
setup(props: IArrayFieldProps, { slots }) {
const formRef = useForm()
const parentRef = useField()
- const basePath =
+
+ const basePath = computed(() =>
props.basePath !== undefined
? props.basePath
: parentRef?.value?.address
- const fieldRef = useAttach(
- () =>
- formRef.value.createArrayField({
- ...props,
- basePath,
- ...getRawComponent(props),
- }),
- [() => props.name, formRef]
+ )
+ const createField = () =>
+ formRef.value.createArrayField({
+ ...props,
+ basePath: basePath.value,
+ ...getRawComponent(props),
+ })
+ const [fieldRef, checker] = useAttach(createField())
+ watch(
+ () => props,
+ () => (fieldRef.value = checker(createField())),
+ { deep: true }
+ )
+ watch(
+ [formRef, parentRef],
+ () => (fieldRef.value = checker(createField()))
)
provide(FieldSymbol, fieldRef)
diff --git a/packages/vue/src/components/Field.ts b/packages/vue/src/components/Field.ts
index c8d079b721d..6c936a71c05 100644
--- a/packages/vue/src/components/Field.ts
+++ b/packages/vue/src/components/Field.ts
@@ -1,4 +1,4 @@
-import { provide, defineComponent, computed } from 'vue-demi'
+import { provide, defineComponent, watch, computed } from 'vue-demi'
import { useField, useForm } from '../hooks'
import { useAttach } from '../hooks/useAttach'
import { FieldSymbol } from '../shared/context'
@@ -62,19 +62,23 @@ export default defineComponent({
setup(props: IFieldProps, { slots }) {
const formRef = useForm()
const parentRef = useField()
+
const basePath = computed(() =>
props.basePath !== undefined ? props.basePath : parentRef?.value?.address
)
-
- const fieldRef = useAttach(
- () =>
- formRef.value.createField({
- ...props,
- basePath: basePath.value,
- ...getRawComponent(props),
- }),
- [() => props.name, basePath, formRef]
+ const createField = () =>
+ formRef.value.createField({
+ ...props,
+ basePath: basePath.value,
+ ...getRawComponent(props),
+ })
+ const [fieldRef, checker] = useAttach(createField())
+ watch(
+ () => props,
+ () => (fieldRef.value = checker(createField())),
+ { deep: true }
)
+ watch([formRef, parentRef], () => (fieldRef.value = checker(createField())))
provide(FieldSymbol, fieldRef)
diff --git a/packages/vue/src/components/FormProvider.ts b/packages/vue/src/components/FormProvider.ts
index 1848eff8904..465e1f56b49 100644
--- a/packages/vue/src/components/FormProvider.ts
+++ b/packages/vue/src/components/FormProvider.ts
@@ -1,4 +1,4 @@
-import { provide, defineComponent, toRaw } from 'vue-demi'
+import { provide, defineComponent, watch } from 'vue-demi'
import { FormSymbol } from '../shared/context'
import { IProviderProps } from '../types'
import { useAttach } from '../hooks/useAttach'
@@ -17,10 +17,13 @@ export default defineComponent({
},
},
setup(props: IProviderProps, { attrs, slots }) {
- const formRef = useAttach(
- () => toRaw(props.form),
- () => props.form
+ const getForm = () => props.form
+ const [formRef, checker] = useAttach(getForm())
+ watch(
+ () => props.form,
+ () => (formRef.value = checker(getForm()))
)
+
provide(FormSymbol, formRef)
return () => h(Fragment, { attrs }, slots)
diff --git a/packages/vue/src/components/ObjectField.ts b/packages/vue/src/components/ObjectField.ts
index 0e6daba6c3a..fefb2ed811a 100644
--- a/packages/vue/src/components/ObjectField.ts
+++ b/packages/vue/src/components/ObjectField.ts
@@ -1,4 +1,4 @@
-import { provide, defineComponent } from 'vue-demi'
+import { provide, defineComponent, computed, watch } from 'vue-demi'
import { useField, useForm } from '../hooks'
import { useAttach } from '../hooks/useAttach'
import ReactiveField from './ReactiveField'
@@ -64,18 +64,27 @@ export default observer(
setup(props: IObjectFieldProps, { slots }) {
const formRef = useForm()
const parentRef = useField()
- const basePath =
+
+ const basePath = computed(() =>
props.basePath !== undefined
? props.basePath
: parentRef?.value?.address
- const fieldRef = useAttach(
- () =>
- formRef.value.createObjectField({
- ...props,
- basePath,
- ...getRawComponent(props),
- }),
- [() => props.name, formRef]
+ )
+ const createField = () =>
+ formRef.value.createObjectField({
+ ...props,
+ basePath: basePath.value,
+ ...getRawComponent(props),
+ })
+ const [fieldRef, checker] = useAttach(createField())
+ watch(
+ () => props,
+ () => (fieldRef.value = checker(createField())),
+ { deep: true }
+ )
+ watch(
+ [formRef, parentRef],
+ () => (fieldRef.value = checker(createField()))
)
provide(FieldSymbol, fieldRef)
diff --git a/packages/vue/src/components/VoidField.ts b/packages/vue/src/components/VoidField.ts
index a035fb38131..3561614d1f2 100644
--- a/packages/vue/src/components/VoidField.ts
+++ b/packages/vue/src/components/VoidField.ts
@@ -1,4 +1,4 @@
-import { provide, defineComponent } from 'vue-demi'
+import { provide, defineComponent, computed, watch } from 'vue-demi'
import { useField, useForm } from '../hooks'
import { useAttach } from '../hooks/useAttach'
import ReactiveField from './ReactiveField'
@@ -50,17 +50,23 @@ export default defineComponent({
setup(props: IVoidFieldProps, { slots }) {
const formRef = useForm()
const parentRef = useField()
- const basePath =
+
+ const basePath = computed(() =>
props.basePath !== undefined ? props.basePath : parentRef?.value?.address
- const fieldRef = useAttach(
- () =>
- formRef.value.createVoidField({
- ...props,
- basePath,
- ...getRawComponent(props),
- }),
- [() => props.name, formRef]
)
+ const createField = () =>
+ formRef.value.createVoidField({
+ ...props,
+ basePath: basePath.value,
+ ...getRawComponent(props),
+ })
+ const [fieldRef, checker] = useAttach(createField())
+ watch(
+ () => props,
+ () => (fieldRef.value = checker(createField())),
+ { deep: true }
+ )
+ watch([formRef, parentRef], () => (fieldRef.value = checker(createField())))
provide(FieldSymbol, fieldRef)
diff --git a/packages/vue/src/hooks/useAttach.ts b/packages/vue/src/hooks/useAttach.ts
index bff3bd5b0c8..cdb5158271f 100644
--- a/packages/vue/src/hooks/useAttach.ts
+++ b/packages/vue/src/hooks/useAttach.ts
@@ -1,4 +1,4 @@
-import { onBeforeUnmount, onMounted, Ref, shallowRef, watch } from 'vue-demi'
+import { onBeforeUnmount, onMounted, shallowRef, Ref } from 'vue-demi'
interface IRecycleTarget {
onMount: () => void
@@ -6,13 +6,10 @@ interface IRecycleTarget {
}
export const useAttach = (
- creator: () => T,
- dependencies: Parameters[0]
-): Ref => {
+ target: T
+): [Ref, (arg: T) => T] => {
const oldTargetRef = shallowRef(null)
- const target = creator()
oldTargetRef.value = target
-
onMounted(() => {
target.onMount()
})
@@ -21,21 +18,16 @@ export const useAttach = (
oldTargetRef.value?.onUnmount()
})
- watch(dependencies, (cur, prev, onInvalidate) => {
- const target = creator()
-
+ const checker = (target: T) => {
if (target !== oldTargetRef.value) {
if (oldTargetRef.value) {
oldTargetRef.value.onUnmount()
}
oldTargetRef.value = target
target.onMount()
-
- onInvalidate(() => {
- oldTargetRef.value?.onUnmount()
- })
}
- })
+ return oldTargetRef.value as T
+ }
- return oldTargetRef
+ return [oldTargetRef, checker]
}