重构一个老旧组件
最近接手了一个三年前写的表单组件,1200 行代码,没有任何类型定义,变量名各种 data1、tmp、flag。改的时候想砸键盘。
原始代码长什么样
// 真实的代码片段
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, /* 缺了一堆依赖 */]);
// ...
}
问题:
- 状态命名毫无意义
- useEffect 依赖缺失
- 所有逻辑塞一块儿
- 没有类型定义
重构步骤
第一步:加类型
先跑一遍 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 行,可读性好多了。关键是有了类型定义,改代码心里有底。
教训
- 写代码的时候多为接手的人想想
- 命名真的很重要
- 定期重构,别等到烂得没法改了再说
最后,给这组件加个 TODO:下个版本用 React Hook Form 重写。这个坑先留着。