UNPKG

4.57 kBJavaScriptView Raw
1import React, { useRef, useEffect, useMemo } from 'react';
2import { usePrevious, useDebounce } from './hooks';
3import PropTypes from 'prop-types';
4import { isDeepEqual, combineSchema } from './base/utils';
5import { asField, DefaultFieldUI } from './base/asField';
6import parse from './base/parser';
7import resolve from './base/resolve';
8import { getValidateList } from './base/validate';
9import fetcher from './HOC/fetcher';
10import './atom.css';
11import './index.css';
12
13function RenderField({ fields, onChange, ...settings }) {
14 const { Field, props } = parse(settings, fields);
15 if (!Field) {
16 return null;
17 }
18 return (
19 <Field
20 isRoot
21 {...props}
22 value={settings.data}
23 onChange={onChange}
24 formData={settings.formData}
25 />
26 );
27}
28
29// 在顶层将 propsSchema 和 uiSchema 合并,便于后续处理。 也可直接传入合并的 schema
30const Wrapper = ({
31 schema,
32 propsSchema = {},
33 uiSchema = {},
34 readOnly,
35 showValidate,
36 ...rest
37}) => {
38 let _schema = {};
39 const jsonSchema = schema || propsSchema; // 兼容schema字段和propsSchema字段
40 // 将uiSchema和schema合并(推荐不写uiSchema)
41 _schema = combineSchema(jsonSchema, uiSchema);
42
43 return (
44 <FormRender
45 readOnly={readOnly}
46 showValidate={!readOnly && showValidate} // 预览模式下不展示校验
47 {...rest}
48 schema={_schema}
49 />
50 );
51};
52
53function FormRender({
54 name = '$form',
55 column = 1,
56 className,
57 schema = {},
58 formData = {},
59 widgets = {},
60 FieldUI = DefaultFieldUI,
61 fields = {},
62 mapping = {},
63 showDescIcon = false,
64 showValidate = true,
65 displayType = 'column',
66 onChange = () => {},
67 onValidate = () => {},
68 onMount,
69 readOnly = false,
70 labelWidth = 110,
71 useLogger = false,
72}) {
73 const isUserInput = useRef(false); // 状态改变是否来自于用户操作
74 const originWidgets = useRef();
75 const generatedFields = useRef({});
76 const previousSchema = usePrevious(schema);
77 const previousData = usePrevious(formData);
78
79 const data = useMemo(() => resolve(schema, formData), [schema, formData]);
80
81 useEffect(() => {
82 if (onMount) {
83 onMount(data);
84 } else {
85 onChange(data);
86 }
87 onValidate(getValidateList(data, schema));
88 }, []);
89
90 useEffect(() => {
91 if (isUserInput.current) {
92 isUserInput.current = false;
93 return;
94 }
95 if (!isDeepEqual(previousSchema, schema)) {
96 onChange(data);
97 updateValidation();
98 } else if (!isDeepEqual(previousData, formData)) {
99 updateValidation();
100 }
101 }, [schema, formData]);
102
103 const debouncedValidate = useDebounce(onValidate);
104
105 // 用户输入都是调用这个函数
106 const handleChange = (key, val) => {
107 isUserInput.current = true;
108 onChange(val);
109 debouncedValidate(getValidateList(val, schema));
110 };
111
112 const updateValidation = () => {
113 onValidate(getValidateList(data, schema));
114 };
115
116 const generated = {};
117 if (!originWidgets.current) {
118 originWidgets.current = widgets;
119 }
120 Object.keys(widgets).forEach(key => {
121 const oWidget = originWidgets.current[key];
122 const nWidget = widgets[key];
123 let gField = generatedFields.current[key];
124 if (!gField || oWidget !== nWidget) {
125 if (oWidget !== nWidget) {
126 originWidgets.current[key] = nWidget;
127 }
128 gField = asField({ FieldUI, Widget: nWidget });
129 generatedFields.current[key] = gField;
130 }
131 generated[key] = gField;
132 });
133
134 const settings = {
135 schema,
136 data,
137 name,
138 column,
139 showDescIcon,
140 showValidate,
141 displayType,
142 readOnly,
143 labelWidth,
144 useLogger,
145 formData: data,
146 };
147
148 const _fields = {
149 // 根据 Widget 生成的 Field
150 generated,
151 // 自定义的 Field
152 customized: fields,
153 // 字段 type 与 widgetName 的映射关系
154 mapping,
155 };
156
157 return (
158 <div className={`${className} fr-wrapper`}>
159 <RenderField {...settings} fields={_fields} onChange={handleChange} />
160 </div>
161 );
162}
163
164FormRender.propTypes = {
165 name: PropTypes.string,
166 column: PropTypes.number,
167 schema: PropTypes.object,
168 formData: PropTypes.object,
169 widgets: PropTypes.objectOf(PropTypes.func),
170 FieldUI: PropTypes.elementType,
171 fields: PropTypes.objectOf(PropTypes.element),
172 mapping: PropTypes.object,
173 showDescIcon: PropTypes.bool,
174 showValidate: PropTypes.bool,
175 displayType: PropTypes.string,
176 onChange: PropTypes.func,
177 onMount: PropTypes.func,
178 onValidate: PropTypes.func,
179 readOnly: PropTypes.bool,
180 labelWidth: PropTypes.number,
181 useLogger: PropTypes.bool,
182};
183
184export default fetcher(Wrapper);