Designable

Designable

对 Schema 节点进行增、删、改操作,并且提供了事件触发机制,用于将数据同步到服务端。

1interface Options {
2  current: Schema;
3  api?: APIClient;
4  onSuccess?: any;
5  refresh?: () => void;
6  t?: any;
7}
8
9interface InsertAdjacentOptions {
10  wrap?: (s: ISchema) => ISchema;
11  removeParentsIfNoChildren?: boolean;
12  breakRemoveOn?: ISchema | BreakFn;
13  onSuccess?: any;
14}
15
16class Designable {
17      constructor(options: Options ) { }
18      loadAPIClientEvents(): void;
19      on(name: 'insertAdjacent' | 'remove' | 'error' | 'patch' | 'batchPatch', listener: any): void
20      emit(name: 'insertAdjacent' | 'remove' | 'error' | 'patch' | 'batchPatch', ...args: any[]): Promise<any>
21      refresh(): void
22      recursiveRemoveIfNoChildren(schema?: Schema, options?: RecursiveRemoveOptions): Schema;
23      remove(schema?: Schema, options?: RemoveOptions): Promise<any>
24      removeWithoutEmit(schema?: Schema, options?: RemoveOptions): Schema
25      insertAdjacent(position: Position, schema: ISchema, options?: InsertAdjacentOptions): void | Promise<any>
26      insertBeforeBeginOrAfterEnd(schema: ISchema, options?: InsertAdjacentOptions): void
27      insertBeforeBegin(schema: ISchema, options?: InsertAdjacentOptions): void
28      insertAfterBegin(schema: ISchema, options?: InsertAdjacentOptions): void
29      insertBeforeEnd(schema: ISchema, options?: InsertAdjacentOptions): Promise<any>
30      insertAfterEnd(schema: ISchema, options?: InsertAdjacentOptions): void
31}

构造函数

  • 参数讲解

    • current:需要操作的 Schema 节点
    • api:用于发起后端请求的 APIClient 实例
    • onSuccess:后端接口请求成功后的回调
    • refresh:用于更新节点后,刷新页面
    • tuseTranslation() 的返回值
  • 示例

1const schema = new Schema({
2  type: 'void',
3  name: 'hello',
4  'x-component': 'div',
5})
6
7const dn = new Designable({ current: schema });

Schema 操作方法

1const schema = new Schema({
2  type: 'void',
3  name: 'a',
4  properties: {
5      b: {
6          type: 'void',
7          properties: {
8              c: {
9                  type: 'void',
10              }
11          }
12      }
13  }
14});
15
16const b = schema.b;
17
18const dn = createDesignable({
19  current: b,
20})
21
22console.log(schema.toJSON());
1import React from 'react';
2import { Schema } from '@tachybase/schema';
3import { createDesignable } from '@tachybase/client';
4
5const schema = new Schema({
6  type: 'void',
7  name: 'a',
8  properties: {
9    b: {
10      type: 'void',
11      properties: {
12        c: {
13          type: 'void',
14        },
15      },
16    },
17  },
18});
19
20const b = schema.properties['b'];
21
22const dn = createDesignable({
23  current: b,
24});
25
26export default () => <pre>{JSON.stringify(schema.toJSON(), null, 2)}</pre>;

remove

移除当前节点

1dn.remove();
2console.log(schema.toJSON());
1import React from 'react';
2import { Schema } from '@tachybase/schema';
3import { createDesignable } from '@tachybase/client';
4
5const schema = new Schema({
6  type: 'void',
7  name: 'a',
8  properties: {
9    b: {
10      type: 'void',
11      properties: {
12        c: {
13          type: 'void',
14        },
15      },
16    },
17  },
18});
19
20const b = schema.properties['b'];
21
22const dn = createDesignable({
23  current: b,
24});
25
26dn.remove();
27
28export default () => <pre>{JSON.stringify(schema.toJSON(), null, 2)}</pre>;
1{
2  type: 'void',
3  name: 'a',
4  properties: {
5-      b: {
6-          type: 'void',
7-          properties: {
8-              c: {
9-                  type: 'void',
10-              }
11-          }
12-      }
13  }
14}

insertBeforeBegin

在当前节点的前面插入,并会触发 insertAdjacent 事件。

1dn.insertBeforeBegin({
2  type: 'void',
3  name: 'd',
4});
5console.log(schema.toJSON());
1import React from 'react';
2import { Schema } from '@tachybase/schema';
3import { createDesignable } from '@tachybase/client';
4
5const schema = new Schema({
6  type: 'void',
7  name: 'a',
8  properties: {
9    b: {
10      type: 'void',
11      properties: {
12        c: {
13          type: 'void',
14        },
15      },
16    },
17  },
18});
19
20const b = schema.properties['b'];
21
22const dn = createDesignable({
23  current: b,
24});
25
26dn.insertBeforeBegin({
27  type: 'void',
28  name: 'd',
29});
30
31export default () => <pre>{JSON.stringify(schema.toJSON(), null, 2)}</pre>;
1{
2  type: 'void',
3  name: 'a',
4  properties: {
5+     d: {
6+       type: 'void',
7+     },
8      b: {
9          type: 'void',
10          properties: {
11              c: {
12                  type: 'void',
13              }
14          }
15      }
16  }
17}

insertAfterBegin

在当前节点的前面插入,并会触发 insertAdjacent 事件。

1dn.insertAfterBegin({
2  type: 'void',
3  name: 'd',
4});
5console.log(schema.toJSON());
1import React from 'react';
2import { Schema } from '@tachybase/schema';
3import { createDesignable } from '@tachybase/client';
4
5const schema = new Schema({
6  type: 'void',
7  name: 'a',
8  properties: {
9    b: {
10      type: 'void',
11      properties: {
12        c: {
13          type: 'void',
14        },
15      },
16    },
17  },
18});
19
20const b = schema.properties['b'];
21
22const dn = createDesignable({
23  current: b,
24});
25
26dn.insertAfterBegin({
27  type: 'void',
28  name: 'd',
29});
30
31export default () => <pre>{JSON.stringify(schema.toJSON(), null, 2)}</pre>;
1{
2  type: 'void',
3  name: 'a',
4  properties: {
5      b: {
6          type: 'void',
7          properties: {
8+             d: {
9+               type: 'void',
10+             },
11              c: {
12                  type: 'void',
13              }
14          }
15      }
16  }
17}

insertBeforeEnd

在当前节点的前面插入,并会触发 insertAdjacent 事件。

1dn.insertBeforeEnd({
2  type: 'void',
3  name: 'd',
4});
5console.log(schema.toJSON());
1import React from 'react';
2import { Schema } from '@tachybase/schema';
3import { createDesignable } from '@tachybase/client';
4
5const schema = new Schema({
6  type: 'void',
7  name: 'a',
8  properties: {
9    b: {
10      type: 'void',
11      properties: {
12        c: {
13          type: 'void',
14        },
15      },
16    },
17  },
18});
19
20const b = schema.properties['b'];
21
22const dn = createDesignable({
23  current: b,
24});
25
26dn.insertBeforeEnd({
27  type: 'void',
28  name: 'd',
29});
30
31export default () => <pre>{JSON.stringify(schema.toJSON(), null, 2)}</pre>;
1{
2  type: 'void',
3  name: 'a',
4  properties: {
5      b: {
6          type: 'void',
7          properties: {
8              c: {
9                  type: 'void',
10              },
11+             d: {
12+               type: 'void',
13+             },
14          }
15      }
16  }
17}

insertAfterEnd

在当前节点的前面插入,并会触发 insertAdjacent 事件。

1dn.insertAfterEnd({
2  type: 'void',
3  name: 'd',
4});
5console.log(schema.toJSON());
1import React from 'react';
2import { Schema } from '@tachybase/schema';
3import { createDesignable } from '@tachybase/client';
4
5const schema = new Schema({
6  type: 'void',
7  name: 'a',
8  properties: {
9    b: {
10      type: 'void',
11      properties: {
12        c: {
13          type: 'void',
14        },
15      },
16    },
17  },
18});
19
20const b = schema.properties['b'];
21
22const dn = createDesignable({
23  current: b,
24});
25
26dn.insertAfterEnd({
27  type: 'void',
28  name: 'd',
29});
30
31export default () => <pre>{JSON.stringify(schema.toJSON(), null, 2)}</pre>;
1{
2  type: 'void',
3  name: 'a',
4  properties: {
5      b: {
6          type: 'void',
7          properties: {
8              c: {
9                  type: 'void',
10              }
11          }
12      },
13+     d: {
14+       type: 'void',
15+     },
16  }
17}

insertAdjacent

根据第一个参数决定插入的位置,是前面四个方法的封装。

1class Designable {
2    insertAdjacent(position: Position, schema: ISchema, options?: InsertAdjacentOptions): void | Promise<any>
3}

事件监听和 API 请求

  • on :添加事件监听的基础方法
  • loadAPIClientEvents:调用 on 方法添加对 insertAdjacentpatchbatchPatchremove 的事件的监听,主要功能是将变更的 Schema 更新到服务端
  • emit:是根据事件名称,调用之前注册过的方法,具体是由前面讲过的 插入操作和删除操作 触发

loadAPIClientEvents() 并非在初始化时调用,需要手动调用,换而言之,如果不调用 dn.loadAPIClientEvents(),则不会将更新发送到服务端,主要是简化在单测或者 DEMO 环境对服务端的 Mock。

工具函数

createDesignable()

new Designable() 的简单封装。

1function createDesignable(options: CreateDesignableProps) {
2  return new Designable(options);
3}
1const dn = createDesignable({ current: schema });

Hooks

useFieldSchema()

用户获取当前节点 Schema JSON 对象,更多信息请参考 formily useFieldSchema()

  • 类型
1import { Schema } from '@tachybase/schema';
2
3const useFieldSchema: () => Schema;
  • 示例
1/**
2 * defaultShowCode: true
3 */
4import React from 'react';
5import { useFieldSchema } from '@formily/react';
6import { Application, Plugin, SchemaComponent } from '@tachybase/client';
7const Demo = ({ children }) => {
8 const fieldSchema = useFieldSchema();
9 return <div style={{ border: '1px solid red' }}>
10  <pre>{ JSON.stringify(fieldSchema, null, 2)}</pre>
11  <div style={{ paddingLeft: 20 }}>{children}</div>
12 </div>
13}
14const schema = {
15    type: 'void',
16    name: 'hello',
17    'x-component': 'Demo',  // 这里是 Demo 组件
18    'properties': {
19        'world': {
20            'type': 'void',
21            'x-component': 'Demo',  // 这里也是 Demo 组件
22        },
23    }
24}
25
26const Root = () => {
27    return <SchemaComponent components={{ Demo }} schema={schema} />
28}
29
30const app = new Application({
31    providers: [Root]
32})
33
34export default app.getRootComponent();

useField()

获取当前节点 Schema 实例,更多信息请参考 formily useField()

  • 类型
1import { GeneralField } from '@formily/core';
2const useField: <T = GeneralField>() => T;
  • 示例
1const Demo = () => {
2 const field = useField();
3 console.log('field', field);
4 return <div></div>
5}
6
7const schema = {
8    type: 'void',
9    name: 'hello',
10    'x-component': 'Demo',  // 这里是 Demo 组件
11    'properties': {
12        'world': {
13            'type': 'void',
14            'x-component': 'Demo',  // 这里也是 Demo 组件
15        },
16    }
17}
18
19const Root = () => {
20    return <SchemaComponent components={{ Hello }} schema={schema} />
21}

useDesignable()

对当前 Schema 节点的修改操作。

  • 类型
1interface InsertAdjacentOptions {
2  wrap?: (s: ISchema) => ISchema;
3  removeParentsIfNoChildren?: boolean;
4  breakRemoveOn?: ISchema | BreakFn;
5  onSuccess?: any;
6}
7
8type Position = 'beforeBegin' | 'afterBegin' | 'beforeEnd' | 'afterEnd';
9
10function useDesignable(): {
11    dn: Designable;
12    designable: boolean;
13    reset: () => void;
14    refresh: () => void;
15    setDesignable: (value: boolean) => void;
16    findComponent(component: any): any;
17    patch: (key: ISchema | string, value?: any) => void
18    on(name: "error" | "insertAdjacent" | "remove" | "patch" | "batchPatch", listener: any): void
19    remove(schema?: any, options?: RemoveOptions): void
20    insertAdjacent(position: Position, schema: ISchema, options?: InsertAdjacentOptions): void
21    insertBeforeBegin(schema: ISchema): void;
22    insertAfterBegin(schema: ISchema): void;
23    insertBeforeEnd(schema: ISchema): void;
24    insertAfterEnd(schema: ISchema): void;
25}
  • 详细解释

    • designable、reset、refresh、setDesignable:这些值继承自 SchemaComponentContext
    • dn:是 Designable 的实例
    • findComponent:用于查找 Schema 中字符串对应真正的组件,如果组件未注册则返回 null
    • remove:内部调用的是 dn.remove 方法
    • on:内部调用的是 dn.on 方法
    • insertAdjacent:插入新的 Schema 节点,内部调用的是 dn.insertAdjacent 方法
      • position:插入位置
      • schema:新的 Schema 节点
      • Options
        • wrap:对 Schema 的二次处理的回调函数
        • removeParentsIfNoChildren:当没有子元素时,删除父元素
        • breakRemoveOn:停止删除的判断回调
        • onSuccess:插入成功的回调
    • insertBeforeBegin:内部调用的是 dn.insertBeforeBegin 方法
    • insertAfterBegin:内部调用的是 dn.insertAfterBegin 方法
    • insertBeforeEnd:内部调用的是 dn.insertBeforeEnd 方法
    • insertAfterEnd:内部调用的是 dn.insertAfterEnd 方法
  • 示例

插入节点。

1import React from 'react';
2import {
3  SchemaComponentProvider,
4  SchemaComponent,
5  useDesignable,
6} from '@tachybase/client';
7import { observer, Schema, useFieldSchema } from '@formily/react';
8import { Button, Space } from 'antd';
9import { uid } from '@formily/shared';
10
11const Hello = observer(
12  (props) => {
13    const { insertAdjacent } = useDesignable();
14    const fieldSchema = useFieldSchema();
15    return (
16      <div>
17        <h1>{fieldSchema.name}</h1>
18        <Space>
19          <Button
20            onClick={() => {
21              insertAdjacent('beforeBegin', {
22                'x-component': 'Hello',
23              });
24            }}
25          >
26            before begin
27          </Button>
28          <Button
29            onClick={() => {
30              insertAdjacent('afterBegin', {
31                'x-component': 'Hello',
32              });
33            }}
34          >
35            after begin
36          </Button>
37          <Button
38            onClick={() => {
39              insertAdjacent('beforeEnd', {
40                'x-component': 'Hello',
41              });
42            }}
43          >
44            before end
45          </Button>
46          <Button
47            onClick={() => {
48              insertAdjacent('afterEnd', {
49                'x-component': 'Hello',
50              });
51            }}
52          >
53            after end
54          </Button>
55        </Space>
56        <div style={{ margin: 50 }}>{props.children}</div>
57      </div>
58    );
59  },
60  { displayName: 'Hello' },
61);
62
63const Page = observer(
64  (props) => {
65    return <div>{props.children}</div>;
66  },
67  { displayName: 'Page' },
68);
69
70export default () => {
71  return (
72    <SchemaComponentProvider components={{ Page, Hello }}>
73      <SchemaComponent
74        schema={{
75          type: 'void',
76          name: 'page',
77          'x-component': 'Page',
78          properties: {
79            hello1: {
80              type: 'void',
81              'x-component': 'Hello',
82            },
83          },
84        }}
85      />
86    </SchemaComponentProvider>
87  );
88};

部分更新。

1import React from 'react';
2import {
3  SchemaComponentProvider,
4  SchemaComponent,
5  FormItem,
6  useDesignable,
7} from '@tachybase/client';
8import { observer, Schema, useField, useFieldSchema, uid } from '@tachybase/schema';
9import { Button } from 'antd';
10
11const Hello = observer(
12  (props) => {
13    const fieldSchema = useFieldSchema();
14    const field = useField();
15    const { dn } = useDesignable();
16    return (
17      <div>
18        <h1>{field.title}</h1>
19        { JSON.stringify(props) }
20        <br/>
21        { JSON.stringify(field.componentProps) }
22        <br/>
23        { JSON.stringify(field.decoratorProps) }
24        <br/>
25        { JSON.stringify(fieldSchema.toJSON()) }
26        <br/>
27        <Button onClick={() => {
28          dn.shallowMerge({
29            title: uid(),
30            'x-component-props': {a: uid(), },
31            'x-decorator-props': {b: uid(), },
32          });
33        }}>shallowMerge</Button>
34        <Button onClick={() => {
35          dn.deepMerge({
36            title: uid(),
37            'x-component-props': {c: uid() },
38            'x-decorator-props': {d: uid(), },
39          });
40        }}>deepMerge</Button>
41      </div>
42    );
43  },
44  { displayName: 'Hello' },
45);
46
47const Page = observer(
48  (props) => {
49    return <div>{props.children}</div>;
50  },
51  { displayName: 'Page' },
52);
53
54export default () => {
55  return (
56    <SchemaComponentProvider components={{ FormItem, Page, Hello }}>
57      <SchemaComponent
58        schema={{
59          type: 'void',
60          name: 'page',
61          'x-component': 'Page',
62          properties: {
63            hello1: {
64              type: 'string',
65              title: 'Title 1',
66              'x-decorator': 'FormItem',
67              'x-component': 'Hello',
68            },
69          },
70        }}
71      />
72    </SchemaComponentProvider>
73  );
74};