如何让自定义表单组件同时支持受控和非受控模式?
最近在封装一个可复用的输入框组件,想让它既能作为受控组件接收value和onChange,又能当非受控组件直接用。尝试用默认props和useState结合,但发现当父组件没传value时,输入框的值不会更新到内部状态。比如这样写:
function Input({ value: propsValue, onChange }) {
const [value, setValue] = useState(propsValue || '');
const handleChange = (e) => {
setValue(e.target.value);
onChange?.(e);
};
return <input value={value} onChange={handleChange} />;
}
测试时发现,如果父组件没传value,输入内容后状态能更新,但再次用propsValue传值时,输入框会闪现旧值。是不是哪里逻辑搞错了?
value和onChange必须同步工作,而非受控组件则依赖 DOM 自身的状态。你的问题出在useState初始化时只读取了propsValue的初始值,但没有响应后续的propsValue变化。要解决这个问题,可以用一个额外的
useEffect来监听propsValue的变化,并在必要时同步更新内部状态。同时需要判断当前组件是处于受控模式还是非受控模式。这里是一个可行的实现:核心思路是通过
isControlled判断当前是否为受控模式。如果是受控模式,就直接使用propsValue;如果不是,则用内部的value状态。useEffect用来确保当父组件传入新的propsValue时,能正确同步到内部状态。另外要注意,非受控模式下需要支持
defaultValue属性,这符合 React 官方推荐的最佳实践。如果父组件既没传value也没传defaultValue,输入框会默认为空字符串。这种写法虽然稍微复杂点,但能保证组件的行为完全符合 React 的规范,也不会出现闪现旧值的问题。我之前也踩过类似的坑,后来仔细读了官方文档才搞明白,受控和非受控模式的切换确实需要特别小心。
useRef标记组件是否初始化过,区分受控和非受控模式。props.value 有值就走受控,否则走内部 state。搞定。