mirror of
https://github.com/nocobase/nocobase
synced 2024-11-16 18:46:17 +00:00
12 KiB
12 KiB
Extending Schema Components
In addition to the native html tags, developers can also adapt more custom components to enrich the Schema component library.
Common methods used to extend components are
- connect to access third-party components without intrusion, generally used to adapt field components, and mapProps, mapReadPretty are used with
- observer when the component uses an observable object internally and you want the component to respond to changes to the observable object
The simplest extension
Register a ready-made React component directly into it.
/**
* 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>
);
};
Access to third-party components via connect
/**
* 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>
);
};
Using observer response data
/**
* 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>
);
};
Nested Schema
props.children
nesting for void and object types properties, see nesting of void and object-type schema for examples<RecursionField />
custom nesting, for all types, see nesting of array-type schema
Note:
properties
of schema other than void and object types cannot be rendered directly byprops.children
, but nesting can be resolved using<RecursionField />
- Only schema of type void and object can be used with onlyRenderProperties
<RecursionField schema={schema} onlyRenderProperties />
Nesting of void and object type schema
The properties node can be adapted directly via props.children
/**
* 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>
);
};
Comparison of rendering results by property type
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>
);
};
Nesting of array type schema
Custom nesting can be solved with <RecursionField />
Array element is a string or number
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) => {
// Only one element
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>
);
};
When the Array element is an Object
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();
// The schema of array type cannot be onlyRenderProperties and needs to be converted to object type
const objSchema = new Schema({
type: 'object',
properties: schema.properties,
});
return (
<ul>
{field.value?.map((item, index) => {
// When the Array element is an Object
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>
);
};
Tree structure data
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>
);
};