Skip to content

Commit 883398b

Browse files
authored
feat: optimize ajv validator performance (#145)
* feat: optimize ajv validator performance * docs: add ajv failure sample
1 parent fd822a1 commit 883398b

File tree

4 files changed

+579
-258
lines changed

4 files changed

+579
-258
lines changed

docs/drip-table/props/ajv.md

Lines changed: 254 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ type AjvOptions = {
1717
```jsx
1818
/**
1919
* transform: true
20-
* defaultShowCode: true
20+
* defaultShowCode: false
2121
* hideActions: ["CSB"]
2222
*/
2323
import React from "react";
@@ -79,3 +79,256 @@ const Demo = () => {
7979

8080
export default Demo;
8181
```
82+
83+
## 错误告警
84+
85+
```jsx
86+
/**
87+
* transform: true
88+
* defaultShowCode: false
89+
* hideActions: ["CSB"]
90+
*/
91+
import React from "react";
92+
import DripTable from "drip-table";
93+
import DripTableDriverAntDesign from "drip-table-driver-antd";
94+
import "antd/dist/antd.css";
95+
import "drip-table/dist/index.css";
96+
97+
const schema = {
98+
columns: [
99+
{
100+
key: "mock_1",
101+
title: "商品名称",
102+
dataIndex: "name",
103+
component: "text",
104+
options: {
105+
mode: "single",
106+
maxRow: 1,
107+
},
108+
},
109+
{
110+
key: "mock_2",
111+
title: "商品详情",
112+
align: "center",
113+
dataIndex: "description",
114+
component: "text",
115+
options: {
116+
mode: "single",
117+
ellipsis: true,
118+
maxRow: 1,
119+
},
120+
},
121+
],
122+
};
123+
124+
const dataSource = [
125+
{
126+
id: 1,
127+
name: "商品一",
128+
price: 7999,
129+
status: "onSale",
130+
description: "商品是为了出售而生产的劳动成果,是人类社会生产力发展到一定历史阶段的产物,是用于交换的劳动产品。",
131+
},
132+
];
133+
134+
const Demo = () => {
135+
return (
136+
<DripTable
137+
driver={DripTableDriverAntDesign}
138+
schema={schema}
139+
dataSource={dataSource}
140+
xxx={1} // 多余的数据
141+
/>
142+
);
143+
};
144+
145+
export default Demo;
146+
```
147+
148+
```jsx
149+
/**
150+
* transform: true
151+
* defaultShowCode: false
152+
* hideActions: ["CSB"]
153+
*/
154+
import React from "react";
155+
import DripTable from "drip-table";
156+
import DripTableDriverAntDesign from "drip-table-driver-antd";
157+
import "antd/dist/antd.css";
158+
import "drip-table/dist/index.css";
159+
160+
const schema = {
161+
columns: [
162+
{
163+
key: "mock_1",
164+
title: "商品名称",
165+
dataIndex: "name",
166+
component: "text",
167+
options: {
168+
mode: "single",
169+
maxRow: 1,
170+
},
171+
},
172+
{
173+
key: "mock_2",
174+
title: "商品详情",
175+
align: "center",
176+
dataIndex: "description",
177+
component: "text",
178+
options: {
179+
mode: "single",
180+
ellipsis: true,
181+
maxRow: 1,
182+
someExtraProperty: true, // 多余的字段
183+
},
184+
someExtraProperty: false, // 多余的字段
185+
},
186+
],
187+
};
188+
189+
const dataSource = [
190+
{
191+
id: 1,
192+
name: "商品一",
193+
price: 7999,
194+
status: "onSale",
195+
description: "商品是为了出售而生产的劳动成果,是人类社会生产力发展到一定历史阶段的产物,是用于交换的劳动产品。",
196+
},
197+
];
198+
199+
const Demo = () => {
200+
return (
201+
<DripTable
202+
driver={DripTableDriverAntDesign}
203+
schema={schema}
204+
dataSource={dataSource}
205+
/>
206+
);
207+
};
208+
209+
export default Demo;
210+
```
211+
212+
```jsx
213+
/**
214+
* transform: true
215+
* defaultShowCode: false
216+
* hideActions: ["CSB"]
217+
*/
218+
import React from "react";
219+
import DripTable from "drip-table";
220+
import DripTableDriverAntDesign from "drip-table-driver-antd";
221+
import "antd/dist/antd.css";
222+
import "drip-table/dist/index.css";
223+
224+
const schema = {
225+
columns: [
226+
{
227+
key: "mock_1",
228+
title: "商品名称",
229+
dataIndex: "name",
230+
component: "text",
231+
options: {
232+
mode: "single",
233+
maxRow: 1,
234+
},
235+
},
236+
{
237+
key: "mock_2",
238+
title: "商品详情",
239+
align: "center",
240+
dataIndex: "description",
241+
component: "text",
242+
options: {
243+
mode: "single",
244+
ellipsis: true,
245+
maxRow: 1,
246+
},
247+
},
248+
],
249+
};
250+
251+
const dataSource = [
252+
{
253+
id: 1,
254+
name: "商品一",
255+
price: 7999,
256+
status: "onSale",
257+
description: "商品是为了出售而生产的劳动成果,是人类社会生产力发展到一定历史阶段的产物,是用于交换的劳动产品。",
258+
},
259+
];
260+
261+
const Demo = () => {
262+
return (
263+
<DripTable
264+
driver={DripTableDriverAntDesign}
265+
// schema={schema} // 缺少参数
266+
dataSource={dataSource}
267+
/>
268+
);
269+
};
270+
271+
export default Demo;
272+
```
273+
274+
```jsx
275+
/**
276+
* transform: true
277+
* defaultShowCode: false
278+
* hideActions: ["CSB"]
279+
*/
280+
import React from "react";
281+
import DripTable from "drip-table";
282+
import DripTableDriverAntDesign from "drip-table-driver-antd";
283+
import "antd/dist/antd.css";
284+
import "drip-table/dist/index.css";
285+
286+
const schema = {
287+
columns: [
288+
{
289+
key: "mock_1",
290+
title: "商品名称",
291+
// dataIndex: "name", // 缺少参数
292+
component: "text",
293+
options: {
294+
mode: "single",
295+
maxRow: 1,
296+
},
297+
},
298+
{
299+
key: "mock_2",
300+
title: "商品详情",
301+
align: "center",
302+
dataIndex: "description",
303+
component: "text",
304+
options: {
305+
mode: "single",
306+
ellipsis: true,
307+
maxRow: 1,
308+
},
309+
},
310+
],
311+
};
312+
313+
const dataSource = [
314+
{
315+
id: 1,
316+
name: "商品一",
317+
price: 7999,
318+
status: "onSale",
319+
description: "商品是为了出售而生产的劳动成果,是人类社会生产力发展到一定历史阶段的产物,是用于交换的劳动产品。",
320+
},
321+
];
322+
323+
const Demo = () => {
324+
return (
325+
<DripTable
326+
driver={DripTableDriverAntDesign}
327+
schema={schema}
328+
dataSource={dataSource}
329+
/>
330+
);
331+
};
332+
333+
export default Demo;
334+
```

packages/drip-table/src/drip-table/index.module.less

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
.ajv-error {
1010
border: 1px solid #ff0000;
11+
margin: 0;
1112
padding: 5px 10px;
1213
}
1314

packages/drip-table/src/drip-table/index.tsx

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
type SchemaObject,
2323
} from '@/types';
2424
import { type DripTableDriverTableProps } from '@/types/driver/table';
25-
import { AjvOptions, validateDripTableColumnSchema, validateDripTableProps } from '@/utils/ajv';
25+
import { AjvOptions, validateDripTableColumnSchema, validateDripTableProp, validateDripTableRequiredProps } from '@/utils/ajv';
2626
import ErrorBoundary from '@/components/error-boundary';
2727
import GenericRender, { DripTableGenericRenderElement } from '@/components/generic-render';
2828
import RichText from '@/components/rich-text';
@@ -308,40 +308,48 @@ const DripTable = <
308308
RecordType extends DripTableRecordTypeWithSubtable<DripTableRecordTypeBase, NonNullable<ExtraOptions['SubtableDataSourceKey']>>,
309309
ExtraOptions extends Partial<DripTableExtraOptions> = never,
310310
>(props: DripTableProps<RecordType, ExtraOptions>): JSX.Element => {
311-
if (props.schema && props.schema.columns.some(c => c['ui:type'] || c['ui:props'])) {
311+
if (props.schema?.columns?.some(c => c['ui:type'] || c['ui:props'])) {
312312
props = {
313313
...props,
314-
schema: {
315-
...props.schema,
316-
columns: props.schema.columns.map((column) => {
314+
schema: Object.assign(
315+
{},
316+
props.schema,
317+
{
318+
columns: props.schema?.columns?.map((column) => {
317319
// 兼容旧版本数据
318-
if ('ui:type' in column || 'ui:props' in column) {
319-
const key = column.key;
320-
if ('ui:type' in column) {
321-
console.warn(`[DripTable] Column ${key} "ui:type" is deprecated, please use "component" instead.`);
322-
}
323-
if ('ui:props' in column) {
324-
console.warn(`[DripTable] Column ${key} "ui:props" is deprecated, please use "options" instead.`);
320+
if ('ui:type' in column || 'ui:props' in column) {
321+
const key = column.key;
322+
if ('ui:type' in column) {
323+
console.warn(`[DripTable] Column ${key} "ui:type" is deprecated, please use "component" instead.`);
324+
}
325+
if ('ui:props' in column) {
326+
console.warn(`[DripTable] Column ${key} "ui:props" is deprecated, please use "options" instead.`);
327+
}
328+
return {
329+
...Object.fromEntries(Object.entries(column).filter(([k]) => k !== 'ui:type' && k !== 'ui:props')),
330+
options: column['ui:props'] || column.options || void 0,
331+
component: column['ui:type'] || column.component,
332+
} as typeof column;
325333
}
326-
return {
327-
...Object.fromEntries(Object.entries(column).filter(([k]) => k !== 'ui:type' && k !== 'ui:props')),
328-
options: column['ui:props'] || column.options || void 0,
329-
component: column['ui:type'] || column.component,
330-
} as typeof column;
331-
}
332-
return column;
333-
}),
334-
},
334+
return column;
335+
}),
336+
},
337+
),
335338
};
336339
}
337340

338-
if (props.ajv !== false) {
339-
const errorMessage = validateDripTableProps(props, props.ajv);
341+
const ajv = props.ajv;
342+
if (ajv !== false) {
343+
const errorMessage = validateDripTableRequiredProps(props, ajv)
344+
|| Object.entries(props)
345+
.map(([k, v]) => React.useMemo(() => validateDripTableProp(k, v, ajv), [k, v, ajv]))
346+
.filter(_ => _)
347+
.join('\n');
340348
if (errorMessage) {
341349
return (
342-
<div className={styles['ajv-error']}>
343-
{ `Props validate failed: ${errorMessage}` }
344-
</div>
350+
<pre className={styles['ajv-error']}>
351+
{ `Props validate failed: ${errorMessage.includes('\n') ? '\n' : ''}${errorMessage}` }
352+
</pre>
345353
);
346354
}
347355
}
@@ -402,9 +410,9 @@ const DripTable = <
402410
const errorMessage = validateDripTableColumnSchema(schema, BuiltInComponent.schema, props.ajv);
403411
if (errorMessage) {
404412
return () => (
405-
<div className={styles['ajv-error']}>
413+
<pre className={styles['ajv-error']}>
406414
{ `Schema validate failed: ${errorMessage}` }
407-
</div>
415+
</pre>
408416
);
409417
}
410418
}
@@ -427,9 +435,9 @@ const DripTable = <
427435
const errorMessage = validateDripTableColumnSchema(schema, ExtraComponent.schema, props.ajv);
428436
if (errorMessage) {
429437
return () => (
430-
<div className={styles['ajv-error']}>
438+
<pre className={styles['ajv-error']}>
431439
{ `Schema validate failed: ${errorMessage}` }
432-
</div>
440+
</pre>
433441
);
434442
}
435443
}

0 commit comments

Comments
 (0)