前端面试-请问你知道受控与非受控组件的差异吗??
大家在面试时,有被问到过受控组件与非受控组件的差异吗?在第一次被问这个问题的时候,我是挺迷茫的,受控组件是使用的手动管理的值,而非受控组件则是由dom自身管理,但这个重要吗?今天就来给大家详细介绍一下受控与非受控的差异。
以React中的Input输入框为例,先讲讲受控与非受控组件两者的实现。
受控组件:React使用state保存输入框的值,改变值一般使用onChange来进行对state的手动更新。
非受控组件:dom自身保存值,要使用值的话需要通过ref获取,如果有默认值则用defaultValue去设。
可能有同学要问了,只要能实现输入,用两种方式不是都可以吗?
受控组件是靠组件重新渲染,来展示当前的值,而非受控组件是靠input的自身dom管理。Vue的场景还有些不同,我们到最后再讨论。
非受控组件大家都知道怎么定义,<input />,还可以挂一个ref来拿到dom。
const Input = () => {
const [value, setValue] = useState('')
const onChange = useCallback((e) => {
setValue(e.target.value);
}, []);
return <input onChange={onChange} value={value} />
} 这里就是给input传入value,并在onChange事件中修改其value。
大家都知道,React中一旦state变化就会导致组件重新渲染,这里的逻辑也是一样。定义state,监听onChange来改变state,从而使组件重新渲染出新值的value。
比较完整的做法可以参考rc-input
https://github.com/react-component/input/blob/master/src/Input.tsx
摘出一些主要代码看一下(这里没有再贴BaseInput,在BaseInput中其实根据getInputElement做渲染。
rc-input提供了受控和非受控两种模式的封装,如果是选择受控组件,则绑定state使用onChange的方式改变即可,而如果选择非受控组件,则使用defaultValue进行初始化设值,其他的操作都使用useImperativeHandle暴露出的方法来进行对原生dom上的方法的调用
我们可以以focus这个方法为例,rc-input提供了两个关于focus的方法:
- onFocus:如果是非受控组件,利用ref中dom真实元素的focus方法来聚焦
- handleFocus:如果是受控组件,使用state方式来管理是否聚焦,一旦调用方法重新渲染,使用class给一个聚焦的样式
可以以此类推,基本上所有的封装输入组件都是按照这样的模式。
const Input = forwardRef<InputRef, InputProps>((props, ref) => {
const [value, setValue] = useMergedState(props.defaultValue, {
value: props.value,
});
const [focused, setFocused] = useState<boolean>(false);
const inputRef = useRef<HTMLInputElement>(null);
const { onFocus } = props
// 非受控
const onFocus = (option?: InputFocusOptions) => {
if (inputRef.current) {
triggerFocus(inputRef.current, option);
}
};
// 受控
const handleFocus: React.FocusEventHandler<HTMLInputElement> = (e) => {
setFocused(true);
onFocus?.(e);
};
useImperativeHandle(ref, () => ({
focus: onFocus,
input: inputRef.current,
}));
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (props.value === undefined) {
setValue(e.target.value);
}
};
const getInputElement = () => {
return (
<input
onChange={handleChange}
onFocus={handleFocus}
ref={inputRef}
/>
);
};
return (
<BaseInput
inputElement={getInputElement()}
value={fixControlledValue(value)}
focused={focused}
triggerFocus={focus}
/>
);
});
export default Input; 显而易见的,如果是受控组件,每输入一个字就重新渲染一次组件其实是会比较消耗性能的,但实际在项目中,我们使用受控组件还是比非受控组件要多,这是因为现在的项目很多表单都比较复杂,表单项和表单项之间有联动。如果使用非受控组件,实现联动是非常麻烦的。同时,对于表单项需要即时校验的情形,非受控组件实现起来也相对更麻烦一点。
最之前,有说到Vue的场景和React有些不同,因为Vue是以数据原子维度渲染,和React是以组件维度进行渲染,但从这来看,Vue虽然会有Proxy监听的消耗,但是会比React少一些渲染上的消耗。
所以受控与非受控还是按照功能需求来,如果说你的输入框只需要进行默认值设置、输入、在最后获取一下用户输入的值的话,非受控组件可以满足你的需求;如果你需要进行表单联动、通过代码输入信息等功能,则就选择受控组件吧。
还有一个不怎么优雅,但是我认为也可以讨论一下的方法:
在useImperativeHandle中暴露onChange的方法,直接通过修改原生dom的value值来实现值的刷新。
当然,肯定会有人说这个方式会和虚拟DOM的理念有冲突,但是虚拟DOM比原生DOM操作性能好也是一个仁者见仁智者见智的事情,如果在非要大量写受控组件的情景下,想要突破性能瓶颈,这么试试也无妨。
#前端面试#
查看18道真题和解析