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 (
- + ) })