diff --git a/docs/guide/advanced/controlled.md b/docs/guide/advanced/controlled.md
index ad5feccfc2d..79bcc77c45b 100644
--- a/docs/guide/advanced/controlled.md
+++ b/docs/guide/advanced/controlled.md
@@ -245,19 +245,12 @@ export default () => {
}
```
-## Schema fragment linkage
+## Schema fragment linkage (top level control)
-Note that there are several problems with this model
-
-1. When the clip is switched, the previously entered value will still be retained, because there is no display or hidden control, so the value will not be deleted automatically
-
-2. Because the values are mentioned outside and turned into observable data, the onFormValuesChange inside the Form will not be triggered anymore, because the values are passed to the Form as a reference
-
-Although these two problems exist, it is still very practical in some scenarios, such as the configuration panel of the Formily form designer, which is based on the x-component/x-decorator type for x-component-props and x-decorator-props To switch dynamically, the maintainability of the schema is still greatly improved
+The most important thing for fragment linkage is to manually clean up the field model, otherwise the UI cannot be synchronized
```tsx
-import React, { useMemo } from 'react'
-import { observable } from '@formily/reactive'
+import React, { useMemo, useRef } from 'react'
import { createForm } from '@formily/core'
import { createSchemaField, observer } from '@formily/react'
import { Form, FormItem, Input, Select } from '@formily/antd'
@@ -314,15 +307,10 @@ const DYNAMIC_INJECT_SCHEMA = {
},
}
-const SchemaForm = observer(({ values }) => {
- const form = useMemo(
- () =>
- createForm({
- values,
- }),
- [values.type]
- )
-
+export default observer(() => {
+ const oldTypeRef = useRef()
+ const form = useMemo(() => createForm(), [])
+ const currentType = form.values.type
const schema = {
type: 'object',
properties: {
@@ -336,27 +324,135 @@ const SchemaForm = observer(({ values }) => {
'x-decorator': 'FormItem',
'x-component': 'Select',
},
- container: DYNAMIC_INJECT_SCHEMA[values.type],
+ container: DYNAMIC_INJECT_SCHEMA[currentType],
},
}
+ if (oldTypeRef.current !== currentType) {
+ form.clearFormGraph('container.*') //Recycle field model
+ form.deleteValuesIn('container') //Clear field values
+ }
+
+ oldTypeRef.current = currentType
+
return (
)
})
+```
-export default () => {
- const values = useMemo(
- () =>
- observable({
- type: 'type_1',
- }),
- []
- )
- return
+## Schema fragment linkage (custom component)
+
+```tsx
+import React, { useMemo, useState, useEffect } from 'react'
+import { createForm } from '@formily/core'
+import {
+ createSchemaField,
+ RecursionField,
+ useForm,
+ useField,
+ observer,
+} from '@formily/react'
+import { Form, FormItem, Input, Select } from '@formily/antd'
+
+const Custom = observer(() => {
+ const field = useField()
+ const form = useForm()
+ const [schema, setSchema] = useState({})
+
+ useEffect(() => {
+ form.clearFormGraph(`${field.address}.*`) //Recycle field model
+ form.deleteValuesIn(field.path) //clear field values
+ //Can be obtained asynchronously
+ setSchema(DYNAMIC_INJECT_SCHEMA[form.values.type])
+ }, [form.values.type])
+
+ return
+})
+
+const SchemaField = createSchemaField({
+ components: {
+ Input,
+ FormItem,
+ Select,
+ Custom,
+ },
+})
+
+const DYNAMIC_INJECT_SCHEMA = {
+ type_1: {
+ type: 'void',
+ properties: {
+ aa: {
+ type: 'string',
+ title: 'AA',
+ 'x-decorator': 'FormItem',
+ 'x-component': 'Input',
+ 'x-component-props': {
+ placeholder: 'Input',
+ },
+ },
+ },
+ },
+ type_2: {
+ type: 'void',
+ properties: {
+ aa: {
+ type: 'string',
+ title: 'AA',
+ 'x-decorator': 'FormItem',
+ enum: [
+ {
+ label: '111',
+ value: '111',
+ },
+ { label: '222', value: '222' },
+ ],
+ 'x-component': 'Select',
+ 'x-component-props': {
+ placeholder: 'Select',
+ },
+ },
+ bb: {
+ type: 'string',
+ title: 'BB',
+ 'x-decorator': 'FormItem',
+ 'x-component': 'Input',
+ },
+ },
+ },
}
+
+export default observer(() => {
+ const form = useMemo(() => createForm(), [])
+ const schema = {
+ type: 'object',
+ properties: {
+ type: {
+ type: 'string',
+ title: 'Type',
+ enum: [
+ { label: 'type 1', value: 'type_1' },
+ { label: 'type 2', value: 'type_2' },
+ ],
+ 'x-decorator': 'FormItem',
+ 'x-component': 'Select',
+ },
+ container: {
+ type: 'object',
+ 'x-component': 'Custom',
+ },
+ },
+ }
+
+ return (
+
+ )
+})
```
## Field Level Control
diff --git a/docs/guide/advanced/controlled.zh-CN.md b/docs/guide/advanced/controlled.zh-CN.md
index 46f82da82ab..13dfab2c555 100644
--- a/docs/guide/advanced/controlled.zh-CN.md
+++ b/docs/guide/advanced/controlled.zh-CN.md
@@ -245,27 +245,139 @@ export default () => {
}
```
-## Schema 片段联动
+## Schema 片段联动(顶层控制)
-注意,这种模式存在几个问题
+片段联动最重要的是需要手动清理字段模型,否则无法做到 UI 同步
-1. 片段切换的时候,之前输入的值还会保留,因为没有任何显示隐藏的控制,所以不会自动删值
+```tsx
+import React, { useMemo, useRef } from 'react'
+import { createForm } from '@formily/core'
+import { createSchemaField, observer } from '@formily/react'
+import { Form, FormItem, Input, Select } from '@formily/antd'
+
+const SchemaField = createSchemaField({
+ components: {
+ Input,
+ FormItem,
+ Select,
+ },
+})
+
+const DYNAMIC_INJECT_SCHEMA = {
+ type_1: {
+ type: 'void',
+ properties: {
+ aa: {
+ type: 'string',
+ title: 'AA',
+ 'x-decorator': 'FormItem',
+ 'x-component': 'Input',
+ 'x-component-props': {
+ placeholder: 'Input',
+ },
+ },
+ },
+ },
+ type_2: {
+ type: 'void',
+ properties: {
+ aa: {
+ type: 'string',
+ title: 'AA',
+ 'x-decorator': 'FormItem',
+ enum: [
+ {
+ label: '111',
+ value: '111',
+ },
+ { label: '222', value: '222' },
+ ],
+ 'x-component': 'Select',
+ 'x-component-props': {
+ placeholder: 'Select',
+ },
+ },
+ bb: {
+ type: 'string',
+ title: 'BB',
+ 'x-decorator': 'FormItem',
+ 'x-component': 'Input',
+ },
+ },
+ },
+}
+
+export default observer(() => {
+ const oldTypeRef = useRef()
+ const form = useMemo(() => createForm(), [])
+ const currentType = form.values.type
+ const schema = {
+ type: 'object',
+ properties: {
+ type: {
+ type: 'string',
+ title: '类型',
+ enum: [
+ { label: '类型1', value: 'type_1' },
+ { label: '类型2', value: 'type_2' },
+ ],
+ 'x-decorator': 'FormItem',
+ 'x-component': 'Select',
+ },
+ container: DYNAMIC_INJECT_SCHEMA[currentType],
+ },
+ }
-2. 因为将 values 提到了外面,且把它变成了 observable 数据,那么 Form 内部的 onFormValuesChange 不会再触发,因为 values 是作为引用传给 Form 的
+ if (oldTypeRef.current !== currentType) {
+ form.clearFormGraph('container.*') //回收字段模型
+ form.deleteValuesIn('container') //清空字段值
+ }
-虽然存在这两个问题,但是在某些场景上还是很实用的,比如 Formily 表单设计器的配置面板,它对于 x-component-props 和 x-decorator-props 是基于 x-component/x-decorator 类型来动态切换的,对于 schema 可维护性提升还是很大
+ oldTypeRef.current = currentType
+
+ return (
+
+ )
+})
+```
+
+## Schema 片段联动(自定义组件)
```tsx
-import React, { useMemo } from 'react'
+import React, { useMemo, useState, useEffect } from 'react'
import { createForm } from '@formily/core'
-import { createSchemaField, observer } from '@formily/react'
+import {
+ createSchemaField,
+ RecursionField,
+ useForm,
+ useField,
+ observer,
+} from '@formily/react'
import { Form, FormItem, Input, Select } from '@formily/antd'
+const Custom = observer(() => {
+ const field = useField()
+ const form = useForm()
+ const [schema, setSchema] = useState({})
+
+ useEffect(() => {
+ form.clearFormGraph(`${field.address}.*`) //回收字段模型
+ form.deleteValuesIn(field.path) //清空字段值
+ //可以异步获取
+ setSchema(DYNAMIC_INJECT_SCHEMA[form.values.type])
+ }, [form.values.type])
+
+ return
+})
+
const SchemaField = createSchemaField({
components: {
Input,
FormItem,
Select,
+ Custom,
},
})
@@ -315,7 +427,6 @@ const DYNAMIC_INJECT_SCHEMA = {
export default observer(() => {
const form = useMemo(() => createForm(), [])
-
const schema = {
type: 'object',
properties: {
@@ -329,13 +440,16 @@ export default observer(() => {
'x-decorator': 'FormItem',
'x-component': 'Select',
},
- container: DYNAMIC_INJECT_SCHEMA[form.values.type],
+ container: {
+ type: 'object',
+ 'x-component': 'Custom',
+ },
},
}
return (
)
})