2022-12-21 09:46:38 +08:00
# Extending Schema Components
2022-10-31 11:52:17 +08:00
2022-12-21 09:46:38 +08:00
In addition to the native html tags, developers can also adapt more custom components to enrich the Schema component library.
2022-10-31 11:52:17 +08:00
2022-12-21 09:46:38 +08:00
Common methods used to extend components are
2022-10-31 11:52:17 +08:00
2022-12-21 09:46:38 +08:00
- [connect ](https://react.formilyjs.org/api/shared/connect ) to access third-party components without intrusion, generally used to adapt field components, and [mapProps ](https://react.formilyjs.org/api/shared/map-props )[, mapReadPretty ](https://react.formilyjs.org/api/shared/map-read-pretty ) are used with
- [observer ](https://react.formilyjs.org/api/shared/observer ) when the component uses an observable object internally and you want the component to respond to changes to the observable object
2022-10-31 11:52:17 +08:00
2022-12-21 09:46:38 +08:00
## The simplest extension
2022-10-31 11:52:17 +08:00
2022-12-21 09:46:38 +08:00
Register a ready-made React component directly into it.
2022-10-31 11:52:17 +08:00
```tsx
/**
* defaultShowCode: true
*/
import React from 'react';
import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
const Hello = () => < h1 > Hello, world!< / h1 > ;
const schema = {
type: 'void',
name: 'hello',
'x-component': 'Hello',
};
export default () => {
return (
< SchemaComponentProvider components = {{ Hello } } >
< SchemaComponent schema = {schema} / >
< / SchemaComponentProvider >
);
};
```
2022-12-21 09:46:38 +08:00
## Access to third-party components via connect
2022-10-31 11:52:17 +08:00
```tsx
/**
* defaultShowCode: true
*/
import React from 'react';
import { Input } from 'antd'
import { connect, mapProps, mapReadPretty } from '@formily/react';
import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
const ReadPretty = (props) => {
return < div > {props.value}< / div >
};
const SingleText = connect(
Input,
mapProps((props, field) => {
return {
...props,
suffix: '后缀',
}
}),
mapReadPretty(ReadPretty),
);
const schema = {
type: 'object',
properties: {
t1: {
type: 'string',
default: 'hello t1',
'x-component': 'SingleText',
},
t2: {
type: 'string',
default: 'hello t2',
'x-component': 'SingleText',
'x-pattern': 'readPretty',
},
}
};
export default () => {
return (
< SchemaComponentProvider components = {{ SingleText } } >
< SchemaComponent schema = {schema} / >
< / SchemaComponentProvider >
);
};
```
2022-12-21 09:46:38 +08:00
## Using observer response data
2022-10-31 11:52:17 +08:00
```tsx
/**
* defaultShowCode: true
*/
import React from 'react';
import { Input } from 'antd';
import { connect, observer, useForm } from '@formily/react';
import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
const SingleText = connect(Input);
const UsedObserver = observer((props) => {
const form = useForm();
return < div > UsedObserver: {form.values.t1}< / div >
});
const NotUsedObserver = (props) => {
const form = useForm();
return < div > NotUsedObserver: {form.values.t1}< / div >
};
const schema = {
type: 'object',
properties: {
t1: {
type: 'string',
'x-component': 'SingleText',
},
t2: {
type: 'string',
'x-component': 'UsedObserver',
},
t3: {
type: 'string',
'x-component': 'NotUsedObserver',
},
}
};
const components = {
SingleText,
UsedObserver,
NotUsedObserver
};
export default () => {
return (
< SchemaComponentProvider components = {components} >
< SchemaComponent schema = {schema} / >
< / SchemaComponentProvider >
);
};
```
2022-12-21 09:46:38 +08:00
## Nested Schema
2022-10-31 11:52:17 +08:00
2022-12-21 09:46:38 +08:00
- `props.children` nesting for void and object types properties, see [nesting of void and object-type schema ](#void-and-object-type-schema-nesting ) for examples
- `<RecursionField />` custom nesting, for all types, see [nesting of array-type schema ](#nesting-of-array-type-schema )
2022-10-31 11:52:17 +08:00
2022-12-21 09:46:38 +08:00
Note:
2022-10-31 11:52:17 +08:00
2022-12-21 09:46:38 +08:00
- `properties` of schema other than void and object types cannot be rendered directly by `props.children` , but nesting can be resolved using `<RecursionField />`
- Only schema of type void and object can be used with onlyRenderProperties
2022-10-31 11:52:17 +08:00
```tsx | pure
< RecursionField schema = {schema} onlyRenderProperties / >
```
2022-12-21 09:46:38 +08:00
### Nesting of void and object type schema
2022-10-31 11:52:17 +08:00
2022-12-21 09:46:38 +08:00
The properties node can be adapted directly via props.children
2022-10-31 11:52:17 +08:00
```tsx
/**
* defaultShowCode: true
*/
import React from 'react';
import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
// Hello 组件适配了 children, 可以嵌套 properties 了
const Hello = (props) => < h1 > Hello, {props.children}!< / h1 > ;
const World = () => < span > world< / span > ;
const schema = {
type: 'object',
name: 'hello',
'x-component': 'Hello',
properties: {
world: {
type: 'string',
'x-component': 'World',
},
},
};
export default () => {
return (
< SchemaComponentProvider components = {{ Hello , World } } >
< SchemaComponent schema = {schema} / >
< / SchemaComponentProvider >
);
};
```
2022-12-21 09:46:38 +08:00
Comparison of rendering results by property type
2022-10-31 11:52:17 +08:00
```tsx
import React from 'react';
import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
const Hello = (props) => < h1 > Hello, {props.children}!< / h1 > ;
const World = () => < span > world< / span > ;
const schema = {
type: 'object',
properties: {
title1: {
type: 'void',
'x-content': 'Void schema, 渲染 properties',
},
void: {
type: 'void',
name: 'hello',
'x-component': 'Hello',
properties: {
world: {
type: 'void',
'x-component': 'World',
},
},
},
title2: {
type: 'void',
'x-content': 'Object schema, 渲染 properties',
},
object: {
type: 'object',
name: 'hello',
'x-component': 'Hello',
properties: {
world: {
type: 'string',
'x-component': 'World',
},
},
},
title3: {
type: 'void',
'x-content': 'Array schema, 不渲染 properties',
},
array: {
type: 'array',
name: 'hello',
'x-component': 'Hello',
properties: {
world: {
type: 'string',
'x-component': 'World',
},
},
},
title4: {
type: 'void',
'x-content': 'String schema, 不渲染 properties',
},
string: {
type: 'string',
name: 'hello',
'x-component': 'Hello',
properties: {
world: {
type: 'string',
'x-component': 'World',
},
},
},
}
};
export default () => {
return (
< SchemaComponentProvider components = {{ Hello , World } } >
< SchemaComponent schema = {schema} / >
< / SchemaComponentProvider >
);
};
```
2022-12-21 09:46:38 +08:00
### Nesting of array type schema
2022-10-31 11:52:17 +08:00
2022-12-21 09:46:38 +08:00
Custom nesting can be solved with `<RecursionField />`
2022-10-31 11:52:17 +08:00
2022-12-21 09:46:38 +08:00
#### Array element is a string or number
2022-10-31 11:52:17 +08:00
```tsx
import React from 'react';
import { useFieldSchema, Schema, RecursionField, useField, observer, connect } from '@formily/react';
import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
const useValueSchema = () => {
const schema = useFieldSchema();
return schema.reduceProperties((buf, s) => {
if (s['x-component'] === 'Value') {
return s;
}
return buf;
});
};
const ArrayList = observer((props) => {
const field = useField();
const schema = useValueSchema();
return (
< >
String Array
< ul >
{field.value?.map((item, index) => {
2022-12-21 09:46:38 +08:00
// Only one element
2022-10-31 11:52:17 +08:00
return < RecursionField name = {index} schema = {schema} / >
})}
< / ul >
< />
);
});
const Value = connect((props) => {
return < li > value: {props.value}< / li >
});
const schema = {
type: 'object',
properties: {
strArr: {
type: 'array',
default: [1, 2, 3],
'x-component': 'ArrayList',
properties: {
value: {
type: 'number',
'x-component': 'Value',
},
}
},
}
};
export default () => {
return (
< SchemaComponentProvider components = {{ ArrayList , Value } } >
< SchemaComponent schema = {schema} / >
< / SchemaComponentProvider >
);
};
```
2022-12-21 09:46:38 +08:00
#### When the Array element is an Object
2022-10-31 11:52:17 +08:00
```tsx
import React from 'react';
import { useFieldSchema, Schema, RecursionField, useField, observer, connect } from '@formily/react';
import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
const ArrayList = observer((props) => {
const field = useField();
const schema = useFieldSchema();
2022-12-21 09:46:38 +08:00
// The schema of array type cannot be onlyRenderProperties and needs to be converted to object type
2022-10-31 11:52:17 +08:00
const objSchema = new Schema({
type: 'object',
properties: schema.properties,
});
return (
< ul >
{field.value?.map((item, index) => {
2022-12-21 09:46:38 +08:00
// When the Array element is an Object
2022-10-31 11:52:17 +08:00
return (
< RecursionField name = {index} schema = {objSchema} onlyRenderProperties / >
)
})}
< / ul >
);
});
const Value = connect((props) => {
return < li > value: {props.value}< / li >
});
const schema = {
type: 'object',
properties: {
objArr: {
type: 'array',
default: [
{ value: 't1' },
{ value: 't2' },
{ value: 't3' },
],
'x-component': 'ArrayList',
properties: {
value: {
type: 'number',
'x-component': 'Value',
},
}
}
}
};
export default () => {
return (
< SchemaComponentProvider components = {{ ArrayList , Value } } >
< SchemaComponent schema = {schema} / >
< / SchemaComponentProvider >
);
};
```
2022-12-21 09:46:38 +08:00
#### Tree structure data
2022-10-31 11:52:17 +08:00
```tsx
import { ArrayField } from '@formily/core';
import { connect, ISchema, observer, RecursionField, useField, useFieldSchema } from '@formily/react';
import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
import { Table, TableColumnType } from 'antd';
import React from 'react';
const ArrayTable = observer((props: any) => {
const { rowKey } = props;
const field = useField< ArrayField > ();
const schema = useFieldSchema();
const columnSchemas = schema.reduceProperties((buf, s) => {
if (s['x-component'] === 'ArrayTable.Column') {
buf.push(s);
}
return buf;
}, []);
const columns = columnSchemas.map((s) => {
return {
render: (value, record) => {
return < RecursionField name = {record.__path} schema = {s} onlyRenderProperties / > ;
},
} as TableColumnType< any > ;
});
return < Table rowKey = {rowKey} columns = {columns} dataSource = {field.value} / > ;
});
const Value = connect((props) => {
return < li > value: {props.value}< / li > ;
});
const schema: ISchema = {
type: 'object',
properties: {
objArr: {
type: 'array',
default: [
{ __path: '0', id: 1, value: 't1' },
{
__path: '1',
id: 2,
value: 't2',
children: [
{
__path: '1.children.0',
id: 5,
value: 't5',
parentId: 2,
},
],
},
{
__path: '2',
id: 3,
value: 't3',
children: [
{
__path: '2.children.0',
id: 4,
value: 't4',
parentId: 3,
children: [
{
__path: '2.children.0.children.0',
id: 6,
value: 't6',
parentId: 4,
},
{
__path: '2.children.0.children.1',
id: 7,
value: 't7',
parentId: 4,
},
],
},
],
},
],
'x-component': 'ArrayTable',
'x-component-props': {
rowKey: 'id',
},
properties: {
c1: {
type: 'void',
'x-component': 'ArrayTable.Column',
properties: {
value: {
type: 'string',
'x-component': 'Value',
},
},
},
},
},
},
};
export default () => {
return (
< SchemaComponentProvider components = {{ ArrayTable , Value } } >
< SchemaComponent schema = {schema} / >
< / SchemaComponentProvider >
);
};
2022-12-21 09:46:38 +08:00
```