我正在使用 React JS 构建表单创建器。现在我专注于在表单内渲染元素的过程,基本上它是一个包含其他一些 div 的 div,作为一个子元素。 我有我的主要组件(表单)和一些可以是不同组件的子组件,例如文本框或下拉列表等,具有一组通用属性和一组特定属性(例如下拉选项未插入到文本框组件)。
我还从 API 中获取数据,该 API 使用带有“type”参数(“textbox”、“dropdown”等)的 json 进行响应。
我正在 json 上循环,当然我可以检查类型,如果它是文本框,则创建一个文本框,如果它是一个下拉列表,则创建一个下拉列表...但我认为有更好的解决方案。
这是我的代码:
import {useState, useEffect} from 'react';
import axios from 'axios';
import Textbox from './Textbox';
import Dropdown from './Dropdown';
function Form(){
const [loading, setLoading] = useState(true);
const [components, setComponents] = useState([]);
useEffect(() => {
if(loading){
fetchComponents()
}
});
const fetchComponents = () => {
axios({
method: 'get',
url: 'http://localhost:8000/api/position'
})
.then(res => {
setComponents(res.data);
setLoading(false);
});
}
const allItems = () => {
return(
components.map((component, key) => {
/** THIS IS WHERE I WANT TO CREATE THE ELEMENT DYNAMICALLY */
const options = JSON.parse(component.options);
if(component.type === 'textbox'){
return <Textbox
key={component.id}
id={ component.id}
name={component.name}
default={{
x: options.position.x,
y: options.position.y,
width: options.size.width,
height: options.size.height
}}
minWidth={100}
minHeight={100}
resizeGrid={[20, 20]}
dragGrid={[20, 20]}
label={component.label}
value={component.value}
bounds="window"
/>
}
if(component.type === 'dropdown'){
return <Dropdown
key={component.id}
id={ component.id}
name={component.name}
default={{
x: options.position.x,
y: options.position.y,
width: options.size.width,
height: options.size.height
}}
minWidth={100}
minHeight={100}
resizeGrid={[20, 20]}
dragGrid={[20, 20]}
label={component.label}
value={component.value}
dropdown_options={component.dropdown_options}
bounds="window"
/>
}
})
)
}
return(
<div>
<div className="box background-quaderno" style={{height: '500px', width: '500px', position: 'relative', overflow: 'auto', padding: '0'}}>
<div style={{height: '100%', width: '100%', padding: '0px', overflow: 'hidden'}}>
{loading ? <></> : allItems()}
</div>
</div>
</div>
)
}
export default Form;
谢谢!
作为示例,使用 react-declarative 源代码。它支持使用递归渲染嵌套的字段组
import { TypedField, FieldType } from "react-declarative";
const CC_FULL_HEIGHT = "calc(100vh - 80px)";
declare var MonthProgressWrapper;
declare var TimeLossWrapper;
declare var IndicatorProgressWrapper;
declare var IndicatorWaitingWrapper;
declare var IndicatorArchiveWrapper;
declare var IndicatorDoneWrapper;
declare var IndicatorAllWrapper;
export const fields: TypedField[] = [
{
type: FieldType.Group,
fields: [
{
type: FieldType.Group,
fields: [
{
type: FieldType.Group,
columnsOverride: '5',
fields: [
{
type: FieldType.Hero,
height: `calc(${CC_FULL_HEIGHT} / 3)`,
columns: '1',
phoneColumns: '5',
right: '10px',
phoneRight: "0px",
bottom: '10px',
minHeight: '225px',
child: {
type: FieldType.Component,
element: IndicatorArchiveWrapper,
},
},
{
type: FieldType.Hero,
height: `calc(${CC_FULL_HEIGHT} / 3)`,
columns: '1',
phoneColumns: '5',
right: '10px',
phoneRight: "0px",
bottom: '10px',
minHeight: '225px',
child: {
type: FieldType.Component,
element: IndicatorWaitingWrapper,
},
},
{
type: FieldType.Hero,
height: `calc(${CC_FULL_HEIGHT} / 3)`,
columns: '1',
phoneColumns: '5',
right: '10px',
phoneRight: "0px",
bottom: '10px',
minHeight: '225px',
child: {
type: FieldType.Component,
element: IndicatorProgressWrapper,
},
},
{
type: FieldType.Hero,
height: `calc(${CC_FULL_HEIGHT} / 3)`,
columns: '1',
phoneColumns: '5',
right: '10px',
phoneRight: "0px",
bottom: '10px',
minHeight: '225px',
child: {
type: FieldType.Component,
element: IndicatorDoneWrapper,
},
},
{
type: FieldType.Hero,
height: `calc(${CC_FULL_HEIGHT} / 3)`,
columns: '1',
phoneColumns: '5',
bottom: '10px',
minHeight: '225px',
child: {
type: FieldType.Component,
element: IndicatorAllWrapper,
},
},
]
},
{
type: FieldType.Hero,
height: `calc(${CC_FULL_HEIGHT} / 3 * 2)`,
columns: '6',
phoneColumns: '12',
phoneBottom: '10px',
tabletBottom: '10px',
right: '10px',
phoneRight: "0px",
minHeight: '400px',
child: {
type: FieldType.Component,
element: MonthProgressWrapper,
},
},
{
type: FieldType.Hero,
height: `calc(${CC_FULL_HEIGHT} / 3 * 2)`,
columns: '6',
phoneColumns: '12',
phoneBottom: '10px',
tabletBottom: '10px',
minHeight: '400px',
child: {
type: FieldType.Component,
element: TimeLossWrapper,
},
},
]
}
]
},
{
type: FieldType.Group,
fieldBottomMargin: "0",
fieldRightMargin: "0",
columnsOverride: '5',
fields: [
{
type: FieldType.Group,
fieldBottomMargin: "0",
columns: "1",
phoneColumns: "5",
tabletColumns: "5",
fields: [
{
type: FieldType.Line,
lineTransparent: true,
title: "Archive",
},
{
type: FieldType.Slider,
name: "archive_count",
minSlider: 0,
maxSlider: 500,
defaultValue: 50,
},
{
type: FieldType.Combo,
name: "archive_limit_bg",
title: 'Archive limit',
itemList: [
"semi",
"solid",
"unset"
],
defaultValue: "semi",
tr: async (entry) => {
const trMap = {
"semi": "Semi",
"solid": "Solid",
"unset": "Unset",
}
return trMap[entry] || entry;
},
noDeselect: true,
},
{
type: FieldType.Text,
name: "archive_low_limit",
defaultValue: "100",
title: 'Lower archive limit',
inputFormatterAllowed: /^[0-9]/,
inputFormatterTemplate: "0000000000000000",
},
{
type: FieldType.Text,
name: "archive_medium_limit",
defaultValue: "200",
title: 'Average archive limit',
inputFormatterAllowed: /^[0-9]/,
inputFormatterTemplate: "0000000000000000",
},
{
type: FieldType.Text,
name: "archive_high_limit",
defaultValue: "300",
title: 'Upper archive limit',
fieldBottomMargin: "0",
inputFormatterAllowed: /^[0-9]/,
inputFormatterTemplate: "0000000000000000",
},
],
},
{
type: FieldType.Group,
fieldBottomMargin: "0",
columns: "1",
phoneColumns: "5",
tabletColumns: "5",
fields: [
{
type: FieldType.Line,
lineTransparent: true,
title: "Waiting",
},
{
type: FieldType.Slider,
name: "waiting_count",
minSlider: 0,
maxSlider: 500,
defaultValue: 50,
},
{
type: FieldType.Combo,
name: "waiting_limit_bg",
title: 'Waiting limit',
defaultValue: "semi",
itemList: [
"semi",
"solid",
"unset"
],
tr: async (entry) => {
const trMap = {
"semi": "Semi",
"solid": "Solid",
"unset": "Unset",
}
return trMap[entry] || entry;
},
noDeselect: true,
},
{
type: FieldType.Text,
name: "waiting_low_limit",
defaultValue: "100",
title: 'Lower expectation limit',
inputFormatterAllowed: /^[0-9]/,
inputFormatterTemplate: "0000000000000000",
},
{
type: FieldType.Text,
name: "waiting_medium_limit",
defaultValue: "200",
title: 'Average expectation limit',
inputFormatterAllowed: /^[0-9]/,
inputFormatterTemplate: "0000000000000000",
},
{
type: FieldType.Text,
name: "waiting_high_limit",
defaultValue: "300",
title: 'Upper Expectation Limit',
fieldBottomMargin: "0",
inputFormatterAllowed: /^[0-9]/,
inputFormatterTemplate: "0000000000000000",
},
],
},
{
type: FieldType.Group,
fieldBottomMargin: "0",
columns: "1",
phoneColumns: "5",
tabletColumns: "5",
fields: [
{
type: FieldType.Line,
lineTransparent: true,
title: "Progress",
},
{
type: FieldType.Slider,
name: "inprogress_count",
minSlider: 0,
maxSlider: 500,
defaultValue: 50,
},
{
type: FieldType.Combo,
name: "inprogress_limit_bg",
title: 'Progress limit',
defaultValue: "semi",
itemList: [
"semi",
"solid",
"unset",
],
tr: async (entry) => {
const trMap = {
"semi": "Semi",
"solid": "Solid",
"unset": "Unset",
}
return trMap[entry] || entry;
},
noDeselect: true,
},
{
type: FieldType.Text,
name: "inprogress_low_limit",
defaultValue: "100",
title: 'Lower progress limit',
inputFormatterAllowed: /^[0-9]/,
inputFormatterTemplate: "0000000000000000",
},
{
type: FieldType.Text,
name: "inprogress_medium_limit",
defaultValue: "200",
title: 'Average progress limit',
inputFormatterAllowed: /^[0-9]/,
inputFormatterTemplate: "0000000000000000",
},
{
type: FieldType.Text,
name: "inprogress_high_limit",
defaultValue: "300",
title: 'Upper progress limit',
fieldBottomMargin: "0",
inputFormatterAllowed: /^[0-9]/,
inputFormatterTemplate: "000000000000000",
},
]
},
{
type: FieldType.Group,
fieldBottomMargin: "0",
columns: "1",
phoneColumns: "5",
tabletColumns: "5",
fields: [
{
type: FieldType.Line,
lineTransparent: true,
title: "Done",
},
{
type: FieldType.Slider,
name: "done_count",
minSlider: 0,
maxSlider: 500,
defaultValue: 50,
},
{
type: FieldType.Combo,
name: "done_limit_bg",
title: 'Execution limit',
defaultValue: "semi",
itemList: [
"semi",
"solid",
"unset",
],
tr: async (entry) => {
const trMap = {
"semi": "Semi",
"solid": "Solid",
"unset": "Unset",
}
return trMap[entry] || entry;
},
noDeselect: true,
},
{
type: FieldType.Text,
name: "done_low_limit",
title: 'Lower Execution Limit',
defaultValue: '100',
inputFormatterAllowed: /^[0-9]/,
inputFormatterTemplate: "0000000000000000",
},
{
type: FieldType.Text,
name: "done_medium_limit",
title: 'Average Execution Limit',
defaultValue: '200',
inputFormatterAllowed: /^[0-9]/,
inputFormatterTemplate: "0000000000000000",
},
{
type: FieldType.Text,
fieldBottomMargin: "0",
name: "done_high_limit",
title: 'Upper Execution Limit',
defaultValue: '300',
inputFormatterAllowed: /^[0-9]/,
inputFormatterTemplate: "0000000000000000",
},
],
},
{
type: FieldType.Group,
fieldBottomMargin: "0",
columns: "1",
phoneColumns: "5",
tabletColumns: "5",
fields: [
{
type: FieldType.Line,
lineTransparent: true,
title: "Total",
},
{
type: FieldType.Slider,
name: "all_count",
minSlider: 0,
maxSlider: 500,
defaultValue: 50,
},
{
type: FieldType.Combo,
name: "all_limit_bg",
title: 'Limit of all',
defaultValue: "semi",
itemList: [
"semi",
"solid",
"unset",
],
tr: async (entry) => {
const trMap = {
"semi": "Semi",
"solid": "Solid",
"unset": "Unset",
}
return trMap[entry] || entry;
},
noDeselect: true,
},
{
type: FieldType.Text,
name: "all_low_limit",
title: "Lower limit of all",
defaultValue: '100',
inputFormatterAllowed: /^[0-9]/,
inputFormatterTemplate: "0000000000000000",
},
{
type: FieldType.Text,
name: "all_medium_limit",
title: 'Average limit of all',
defaultValue: '200',
inputFormatterAllowed: /^[0-9]/,
inputFormatterTemplate: "0000000000000000",
},
{
type: FieldType.Text,
name: "all_high_limit",
title: 'Upper limit of all',
defaultValue: '300',
fieldBottomMargin: "0",
inputFormatterAllowed: /^[0-9]/,
inputFormatterTemplate: "0000000000000000",
},
],
},
]
},
{
type: FieldType.Group,
desktopColumns: "6",
tabletColumns: "12",
phoneColumns: "12",
fields: [
{
type: FieldType.Line,
lineTransparent: true,
title: "Monthly progress",
},
{
type: FieldType.Slider,
name: "monthprogress_count",
minSlider: 0,
maxSlider: 500,
defaultValue: 50,
},
{
type: FieldType.Text,
name: "monthprogress_low_limit",
defaultValue: '100',
title: 'Lower limit of monthly progress',
inputFormatterAllowed: /^[0-9]/,
inputFormatterTemplate: "0000000000000000",
},
{
type: FieldType.Text,
name: "monthprogress_medium_limit",
title: 'Average progress limit per month',
defaultValue: '200',
inputFormatterAllowed: /^[0-9]/,
inputFormatterTemplate: "0000000000000000",
},
{
type: FieldType.Text,
name: "monthprogress_high_limit",
title: 'Upper limit of monthly progress',
defaultValue: '300',
inputFormatterAllowed: /^[0-9]/,
inputFormatterTemplate: "0000000000000000",
},
],
},
{
type: FieldType.Component,
element: ({ _fieldData: data }) => <pre>{JSON.stringify(data, null, 2)}</pre>,
}
]