#性能优化
Framework:
#概述
Schema FormX 在设计时已考虑性能优化,但在复杂场景下仍可能遇到性能瓶颈。本章节介绍常见的优化策略和最佳实践。
#交互式示例
#依赖联动优化
#问题:过多的联动触发
当表单字段之间存在复杂依赖时,可能会触发过多的联动计算。
优化策略:
- 控制联动深度:避免超过 3-4 层的级联依赖
- 合理拆分 Schema:将复杂表单拆分为多个独立的 Group
- 使用防抖:对频繁触发的联动使用防抖
#优化示例
import { useMemo, useCallback } from 'react';
// ❌ 错误:每次渲染都创建新函数
const schema = [
{
type: 'string',
name: 'field1',
deps: ['field0'],
onDepsChange: async ({ deps }) => {
// 每次都是新函数,可能触发不必要的重新渲染
}
}
];
// ✅ 正确:使用 useCallback 稳定函数引用
const handleDepsChange = useCallback(async ({ deps, schema }) => {
const data = await fetchData(deps[0]);
return { schema: { ...schema, props: { options: data } } };
}, []);
const schema = [
{
type: 'string',
name: 'field1',
deps: ['field0'],
onDepsChange: handleDepsChange
}
];#复杂表单拆分
#方案一:使用分组(Group)
将大表单拆分为多个 Group,每个 Group 独立管理:
import { Form } from '@schema-formx/react';
import type { GroupSchema } from '@schema-formx/react';
const schema: GroupSchema[] = [
{
key: 'basic',
props: { title: '基本信息' },
fields: [
{ type: 'string', name: 'name', label: '姓名' },
{ type: 'string', name: 'email', label: '邮箱' }
]
},
{
key: 'address',
props: { title: '地址信息' },
fields: [
{ type: 'string', name: 'province', label: '省份' },
{ type: 'string', name: 'city', label: '城市' }
]
}
];
function MyForm() {
return <Form schema={schema} />;
}#方案二:拆分为多个 Form
对于完全独立的表单部分,可以使用多个 Form 实例:
import { Form } from '@schema-formx/react';
import { useRef } from 'react';
function MultiFormPage() {
const formRef1 = useRef(null);
const formRef2 = useRef(null);
const handleSubmit = async () => {
// 分别获取两个表单的数据
const result1 = await formRef1.current.validate();
const result2 = await formRef2.current.validate();
if (!result1.errors && !result2.errors) {
console.log('表单1数据:', result1.data);
console.log('表单2数据:', result2.data);
}
};
return (
<div>
<h3>部分一</h3>
<Form
ref={formRef1}
schema={schema1}
components={components}
layout={layout}
onChange={({ data }) => console.log('表单1变化:', data)}
/>
<h3>部分二</h3>
<Form
ref={formRef2}
schema={schema2}
components={components}
layout={layout}
onChange={({ data }) => console.log('表单2变化:', data)}
/>
<button onClick={handleSubmit}>提交</button>
</div>
);
}#异步数据加载优化
#使用缓存避免重复请求
const dataCache = new Map();
const fetchData = async (key: string) => {
if (dataCache.has(key)) {
return dataCache.get(key);
}
const data = await api.getData(key);
dataCache.set(key, data);
return data;
};
const schema = [
{
type: 'string',
name: 'industry',
label: '行业',
props: {
options: ['IT', '金融', '教育']
}
},
{
type: 'string',
name: 'company',
label: '公司',
deps: ['industry'],
onDepsChange: async ({ deps, schema }) => {
const companies = await fetchData(deps[0]);
return {
schema: { ...schema, props: { options: companies } }
};
}
}
];#请求防抖
对于输入框的联动,使用防抖避免频繁触发:
import { debounce } from 'lodash-es';
const fetchSuggestions = debounce(async (keyword: string) => {
const results = await api.search(keyword);
return results;
}, 300);
const schema = [
{
type: 'string',
name: 'username',
label: '用户名',
deps: [],
onDepsChange: async () => {
// 注意:这里简化演示,实际使用需要更复杂的防抖逻辑
}
}
];#渲染优化
#避免不必要的重新渲染
import { useMemo } from 'react';
function MyForm() {
// ✅ 使用 useMemo 缓存 schema,避免每次渲染都创建新对象
const schema = useMemo(() => [
{ type: 'string', name: 'name', label: '姓名' },
{ type: 'string', name: 'email', label: '邮箱' }
], []); // 空依赖数组,只创建一次
// ✅ 使用 useMemo 缓存 layout
const layout = useMemo(() => ({
formClassName: 'my-form',
formStyle: { maxWidth: '600px' }
}), []);
return <Form schema={schema} layout={layout} />;
}#使用 React.memo 包装组件
import { memo } from 'react';
// 使用 React.memo 避免不必要的重新渲染
const CustomField = memo(({ value, onChange }: any) => {
return (
<input
value={value || ''}
onChange={(e) => onChange(e.target.value)}
style={{ padding: '8px', border: '1px solid #ccc' }}
/>
);
});
// 注册自定义组件
const components = {
DefaultControl: CustomField
};#验证优化
#局部验证 vs 全局验证
根据场景选择合适的验证策略:
import { useRef } from 'react';
function MyForm() {
const formRef = useRef(null);
// ✅ 提交时验证所有字段
const handleSubmit = async () => {
const { data, errors } = await formRef.current.validate();
if (errors) {
console.log('验证失败:', errors);
return;
}
console.log('验证通过:', data);
};
return <Form ref={formRef} schema={schema} />;
}#避免不必要的全局验证
当表单字段较多时,避免在每次值变化时触发全局验证。可以在提交时统一验证:
import { useRef, useCallback } from 'react';
function MyForm() {
const formRef = useRef(null);
// ✅ 仅在提交时触发全局验证,而非每次值变化
const handleSubmit = useCallback(async () => {
const { data, errors } = await formRef.current.validate();
if (errors) {
console.log('验证失败:', errors);
return;
}
console.log('验证通过:', data);
}, []);
return (
<>
<Form ref={formRef} schema={schema} />
<button onClick={handleSubmit}>提交</button>
</>
);
}验证策略对比:
| 策略 | 方法 | 适用场景 |
|---|---|---|
| 全局验证 | validate() | 提交表单时 |
| 自动验证 | 字段值变化时自动触发 | 用户输入时(默认行为) |
#最佳实践总结
#性能优化清单
| 场景 | 建议 |
|---|---|
| 复杂依赖 | 控制联动深度,不超过 3-4 层 |
| 大型表单 | 使用 Group 拆分,或拆分为多个 Form |
| 异步请求 | 缓存结果,使用防抖 |
| 频繁渲染 | 使用 useMemo/useCallback |
| 自定义组件 | 使用 React.memo 包装 |
| 验证 | 使用局部验证代替全局验证 |
#性能基准参考
以下数据基于典型场景测试,供优化参考:
| 场景 | 字段数量 | 首次渲染 | 联动触发 | 建议 |
|---|---|---|---|---|
| 简单表单 | < 10 | < 50ms | < 10ms | 无需优化 |
| 中等表单 | 10-50 | 50-200ms | 10-50ms | 建议使用 useMemo |
| 复杂表单 | 50-100 | 200-500ms | 50-100ms | 建议拆分 Group |
| 超大表单 | > 100 | > 500ms | > 100ms | 建议拆分多个 Form |
说明:以上数据为参考值,实际性能取决于字段复杂度、联动逻辑复杂度、机器性能等因素。
#性能监控
使用 React DevTools Profiler 监控表单性能:
import { Profiler } from 'react';
function onRenderCallback(
id, // 发生提交的 Profiler 树的 "id"
phase, // "mount"(首次挂载)或 "update"(重新渲染)
actualDuration, // 本次更新 committed 花费的毫秒数
baseDuration, // 估计不使用 memo 情况下需要的时间
startTime, // 本次更新开始渲染的时间
commitTime // 本次更新 committed 的时间
) {
// 如果渲染时间过长,输出警告
if (actualDuration > 100) {
console.warn(`Form render took ${actualDuration}ms`, {
phase,
fieldsCount: schema.length,
hasComplexDeps: schema.some(f => f.deps?.length > 0)
});
}
}
function MyForm() {
return (
<Profiler id="MyForm" onRender={onRenderCallback}>
<Form schema={schema} components={components} layout={layout} />
</Profiler>
);
}#常见性能问题诊断
| 症状 | 可能原因 | 解决方案 |
|---|---|---|
| 首次渲染慢 | Schema 对象过大 | 使用 useMemo 缓存 |
| 输入卡顿 | onChange 回调执行慢 | 使用 useCallback 缓存 |
| 联动触发过多 | 依赖链过长 | 控制联动深度,使用防抖 |
| 内存占用高 | 组件未正确卸载 | 检查 useEffect 清理 |
| 验证慢 | 全局验证频繁 | 使用局部验证 |
#优化前后对比示例
优化前:
function MyForm() {
// ❌ 每次渲染都创建新对象
const schema = [
{ type: 'string', name: 'name', label: '姓名' },
{ type: 'string', name: 'email', label: '邮箱' }
];
const layout = {
formClassName: 'my-form',
formStyle: { maxWidth: '600px' }
};
// ❌ 每次渲染都创建新函数
const handleChange = ({ data }) => {
console.log(data);
};
return <Form schema={schema} layout={layout} onChange={handleChange} />;
}优化后:
function MyForm() {
// ✅ 使用 useMemo 缓存 schema
const schema = useMemo(() => [
{ type: 'string', name: 'name', label: '姓名' },
{ type: 'string', name: 'email', label: '邮箱' }
], []);
// ✅ 使用 useMemo 缓存 layout
const layout = useMemo(() => ({
formClassName: 'my-form',
formStyle: { maxWidth: '600px' }
}), []);
// ✅ 使用 useCallback 缓存回调
const handleChange = useCallback(({ data }) => {
console.log(data);
}, []);
return <Form schema={schema} layout={layout} onChange={handleChange} />;
}// 开发环境下的性能监控
if (process.env.NODE_ENV === 'development') {
const handleFormRender = ({ data, name }) => {
console.log(`[FormX] Form updated at ${name}`, performance.now());
};
return <Form schema={schema} onChange={handleFormRender} />;
}#性能测试方法
#基准测试代码
使用 performance.now() 测量关键操作耗时:
function benchmarkFormRender(schema, iterations = 100) {
const times = [];
for (let i = 0; i < iterations; i++) {
const start = performance.now();
// 渲染表单
const end = performance.now();
times.push(end - start);
}
const avg = times.reduce((a, b) => a + b) / times.length;
const max = Math.max(...times);
const min = Math.min(...times);
console.log(`Benchmark Results (${iterations} iterations):`);
console.log(` Average: ${avg.toFixed(2)}ms`);
console.log(` Max: ${max.toFixed(2)}ms`);
console.log(` Min: ${min.toFixed(2)}ms`);
return { avg, max, min };
}#使用 React Profiler 测量
import { Profiler } from 'react';
function onRenderCallback(id, phase, actualDuration) {
console.log(`${id} ${phase}: ${actualDuration.toFixed(2)}ms`);
}
// 包裹表单组件
<Profiler id="MyForm" onRender={onRenderCallback}>
<Form schema={schema} components={components} />
</Profiler>#内存使用监控
// 测量内存使用(仅在支持的浏览器中)
function measureMemory() {
if (performance.memory) {
return {
usedJSHeapSize: (performance.memory.usedJSHeapSize / 1048576).toFixed(2) + ' MB',
totalJSHeapSize: (performance.memory.totalJSHeapSize / 1048576).toFixed(2) + ' MB'
};
}
return null;
}#更多优化案例
#案例1:大量字段的懒加载
当表单包含 100+ 字段时,可以使用懒加载策略:
function LazyForm() {
const [visibleRange, setVisibleRange] = useState({ start: 0, end: 20 });
const visibleSchema = useMemo(() =>
schema.slice(visibleRange.start, visibleRange.end),
[visibleRange]
);
const handleScroll = useCallback((e) => {
const { scrollTop, clientHeight } = e.target;
const itemHeight = 60; // 每个字段高度
const start = Math.floor(scrollTop / itemHeight);
const end = Math.ceil((scrollTop + clientHeight) / itemHeight);
setVisibleRange({ start: Math.max(0, start - 5), end: end + 5 });
}, []);
return (
<div onScroll={handleScroll} style={{ height: '600px', overflow: 'auto' }}>
<Form schema={visibleSchema} components={components} />
</div>
);
}#案例2:复杂验证的异步处理
const schema = [
{
type: 'string',
name: 'username',
label: '用户名',
validator: async ({ value }) => {
// 模拟异步验证(如检查用户名是否已存在)
const response = await checkUsernameExists(value);
if (response.exists) {
return '用户名已存在';
}
return undefined;
}
}
];#案例3:条件渲染优化
function ConditionalForm() {
const [showAdvanced, setShowAdvanced] = useState(false);
// ✅ 基础 schema 始终存在
const baseSchema = useMemo(() => [
{ type: 'string', name: 'name', label: '姓名' },
{ type: 'string', name: 'email', label: '邮箱' }
], []);
// ✅ 高级配置仅在需要时添加
const advancedSchema = useMemo(() =>
showAdvanced ? [
{ type: 'string', name: 'phone', label: '电话' },
{ type: 'string', name: 'address', label: '地址' }
] : [],
[showAdvanced]
);
const schema = useMemo(() => [...baseSchema, ...advancedSchema], [baseSchema, advancedSchema]);
return (
<>
<Form schema={schema} />
<button onClick={() => setShowAdvanced(!showAdvanced)}>
{showAdvanced ? '隐藏' : '显示'}高级选项
</button>
</>
);
}