性能优化

Framework:

概述

Schema FormX 在设计时已考虑性能优化,但在复杂场景下仍可能遇到性能瓶颈。本章节介绍常见的优化策略和最佳实践。

交互式示例

依赖联动优化

问题:过多的联动触发

当表单字段之间存在复杂依赖时,可能会触发过多的联动计算。

优化策略

  1. 控制联动深度:避免超过 3-4 层的级联依赖
  2. 合理拆分 Schema:将复杂表单拆分为多个独立的 Group
  3. 使用防抖:对频繁触发的联动使用防抖

优化示例

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-5050-200ms10-50ms建议使用 useMemo
复杂表单50-100200-500ms50-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>
    </>
  );
}

相关文档