如何实现From的受控和非受控模式?
FormItem是如何获取到子组件的数据的?
这里只简单写写原理,暂时不涉及遇到的困难。
- Form用来维护一个大状态,然后通过React.Context来下放一些修改这个状态的方法:比如`getFieldValue(name)`和`setFieldValue(name, value)`等等(还有其他函数,比如校验或者报错等等)
- FormItem通过React.Context来获取到Form的状态,然后根据name来获取到对应的值。 再使用`React.cloneElement`来把获取到的Value和onChange函数传递给包裹Input组件
js
// FormItem 内部简化逻辑
function FormItem({ children, name }) {
// 1. 从 Context 中获取表单状态和方法
const { getFieldValue, setFieldValue } = useContext(FormContext);
// 2. 获取当前字段的值
const value = getFieldValue(name);
// 3. 创建一个新的 onChange 处理器
const handleChange = (e) => {
const newValue = e.target.value; // 简化处理,实际情况更复杂
setFieldValue(name, newValue);
};
// 4. 【核心】克隆子元素,并注入新的 props
const controlledChild = React.cloneElement(children, {
value: value, // 注入从中央 state 来的 value
onChange: handleChange, // 注入我们自己包装过的 onChange
});
// 5. 渲染被“魔改”过的子元素
return (
<div className="form-item">
{/* ... 可能还有 Label, 错误提示等 ... */}
{controlledChild}
</div>
);
}
<Form>
<FormItem name="username">
<Input placeholder="请输入用户名" />
</FormItem>
</Form>
<Input
placeholder="请输入用户名"
value={formState.username} // 来自 Form 的 state
onChange={formItemCreatedChangeHandler} // FormItem 创建的 handler
/>
追问:
“如果 FormItem 的 children 不是一个单一的React元素,而是一个被 div 包裹的元素,或者是一个返回 null 的组件,你的 cloneElement 会不会报错?你该如何使你的 FormItem 更健壮?”
- “拷问”意图: 考察你对 React.Children API 的了解。 React.cloneElement 要求第一个参数必须是合法的React元素。你需要回答使用 React.Children.only 来做校验,或者遍历 children 找到需要注入的那个目标元素,这体现了你的代码鲁棒性思维。
“所有组件都遵循 value / onChange 协议吗?比如 Ant Design 的 Switch 组件,它的值属性是 checked ,变化的回调是 onChange 但参数不是 event 。你的 FormItem 如何适配这种‘非标准’组件?”
- “拷问”意图: 考察你的组件设计灵活性和扩展性。优秀的 FormItem 设计会提供额外的 prop,比如 valuePropName (默认为 'value') 和 trigger (默认为 'onChange'),允许使用者自定义 FormItem 从子组件的哪个 prop 上取值,以及监听哪个 prop 的变化。例如: 。
“你刚才的实现中,每次 FormItem 重新渲染都会创建一个新的 handleChange 函数,这会不会有性能问题?你如何优化?”
- “拷问”意图: 考察你对React性能优化的理解。你需要回答可以使用 useCallback 来缓存这个 handleChange 函数,避免在子组件的 props 上产生不必要的变更,从而减少子组件的重渲染。