重构一个老旧组件

最近接手了一个三年前写的表单组件,1200 行代码,没有任何类型定义,变量名各种 data1tmpflag。改的时候想砸键盘。

原始代码长什么样

// 真实的代码片段
function Form(props) {
  const [data, setData] = useState({});
  const [data2, setData2] = useState({});
  const [flag, setFlag] = useState(false);
  const [flag2, setFlag2] = useState(false);
  // ... 更多 flag

  useEffect(() => {
    if (props.type === 'edit') {
      // 200 行逻辑
    } else if (props.type === 'create') {
      // 又是 150 行
    }
    // else if 继续...
  }, [props.type, props.id, /* 缺了一堆依赖 */]);

  // ...
}

问题:

  1. 状态命名毫无意义
  2. useEffect 依赖缺失
  3. 所有逻辑塞一块儿
  4. 没有类型定义

重构步骤

第一步:加类型

先跑一遍 tsc --init,然后一点点补类型。

interface FormProps {
  type: 'create' | 'edit' | 'view';
  id?: string;
  onSubmit: (data: FormData) => void;
  defaultValues?: Partial<FormData>;
}

interface FormData {
  name: string;
  email: string;
  // ...
}

过程中发现了很多隐式的 bug,比如某个字段可能是 undefined 但代码里直接访问了。

第二步:拆分逻辑

把 useEffect 里的逻辑抽出来:

// 自定义 Hook
function useFormInitialization(type: FormProps['type'], id?: string) {
  const [data, setData] = useState<FormData>(defaultFormData);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    if (type === 'edit' && id) {
      setLoading(true);
      fetchData(id).then(data => {
        setData(data);
        setLoading(false);
      });
    }
  }, [type, id]);

  return { data, setData, loading };
}

主组件清爽了很多。

第三步:整理状态

把散落的状态整合:

// 之前
const [flag, setFlag] = useState(false);
const [flag2, setFlag2] = useState(false);
const [error, setError] = useState('');

// 之后
const [formState, setFormState] = useState({
  isSubmitting: false,
  isValidating: false,
  error: null,
});

用 useReducer 会更清晰,但这组件已经够复杂了,先不动。

第四步:加注释

不是那种废话注释,而是解释”为什么”:

// 这里用 setTimeout 是因为需要等待 DOM 更新后再计算高度
// 如果用 useEffect 会有闪烁
setTimeout(() => {
  calculateHeight();
}, 0);

结果

重构后代码从 1200 行减到 700 行,可读性好多了。关键是有了类型定义,改代码心里有底。

教训

  1. 写代码的时候多为接手的人想想
  2. 命名真的很重要
  3. 定期重构,别等到烂得没法改了再说

最后,给这组件加个 TODO:下个版本用 React Hook Form 重写。这个坑先留着。