前端面试-请问你知道受控与非受控组件的差异吗??

受控与非受控组件的差异

大家在面试时,有被问到过受控组件与非受控组件的差异吗?在第一次被问这个问题的时候,我是挺迷茫的,受控组件是使用的手动管理的值,而非受控组件则是由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的方法:

  1. onFocus:如果是非受控组件,利用ref中dom真实元素的focus方法来聚焦
  2. 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操作性能好也是一个仁者见仁智者见智的事情,如果在非要大量写受控组件的情景下,想要突破性能瓶颈,这么试试也无妨。

#前端面试#
全部评论
学到了,感谢楼主
点赞 回复 分享
发布于 2022-06-27 21:04

相关推荐

程序员小白条:转的话也需要沉默成本,而且你前端转 Java,更死了,测开和前端差不多,竞争力太差的话,哪个岗位都是一样的,没面试
计算机有哪些岗位值得去?
点赞 评论 收藏
分享
评论
1
9
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务