浅谈 React组件设计之受控和非受控组件

简介

React 中的受控和非受控是非常重要的知识吧。当我们在使用一些组件库的时候,比如Antd,是否也关注到了这些库中的组件即支持受控也同样支持非受控呢?

不错,抛开组件库,当我们在开发一些组件时,如果此组件即支持受控也同样支持非受控,那么对于组件的使用者来说,能提高组件更大的灵活性和扩展性以及可复用性。

受控组件:由 prop 驱动

非受控组件:由自定义组件内部的 state 驱动

最后,本文将开发一个示例组件,且同时支持受控和非受控两种模式

受控组件

受控组件是指其状态完全由外部通过 props 控制的组件。通常,这些组件会接收一个值(如 value )和一个事件处理函数(如 onChange),并且组件内部不会维护自己的状态。

示例

一个简单的受控输入框组件:

js 复制代码
import React from 'react';

const ControlledInput = ({ value, onChange }) => {
  return (
    <input type="text" value={value} onChange={onChange} />
  );
};

export default ControlledInput;

使用这个受控组件:

js 复制代码
import React, { useState } from 'react';
import ControlledInput from './ControlledInput';

const App = () => {
  const [inputValue, setInputValue] = useState('');

  const handleChange = (event) => {
    setInputValue(event.target.value);
  };

  return (
    <div>
      <ControlledInput value={inputValue} onChange={handleChange} />
      <p>Current Value: {inputValue}</p>
    </div>
  );
};

export default App;

在这个例子中,ControlledInput 组件的值完全由 App 组件的状态 inputValue 控制。

非受控组件

非受控组件是指其状态由组件内部的 state 控制,外部不直接控制其状态。通常,这些组件会使用 ref 来访问 DOM 元素的值。

示例

一个简单的非受控输入框组件:

js 复制代码
import React, { useRef } from 'react';

const UncontrolledInput = () => {
  const inputRef = useRef(null);

  const handleClick = () => {
    alert(`Current Value: ${inputRef.current.value}`);
  };

  return (
    <div>
      <input type="text" ref={inputRef} />
      <button onClick={handleClick}>Show Value</button>
    </div>
  );
};

export default UncontrolledInput;

使用这个非受控组件:

js 复制代码
import React from 'react';
import UncontrolledInput from './UncontrolledInput';

const App = () => {
  return (<UncontrolledInput />);
};

export default App;

在这个例子中,UncontrolledInput 组件的值由组件内部的 ref 控制,外部组件无法直接控制它。

组件同时支持受控和非受控两种模式

可以在组件中检查是否传递了控制属性(例如 value),如果传递了,就作为受控组件处理;如果没有传递,就作为非受控组件处理。

通过以下步骤来实现这一点:

  1. 检查是否传递了控制属性。
  2. 如果传递了控制属性,就使用该属性作为组件的值。
  3. 如果没有传递控制属性,就使用组件内部的状态来管理值。

示例代码

js 复制代码
import React, { useState, useRef, useEffect } from 'react';

const HybridInput = ({ value, defaultValue, onChange }) => {
  // 内部状态用于非受控模式
  const [internalValue, setInternalValue] = useState(defaultValue || '');
  // 受控模式检查
  const isControlled = value !== undefined;

  // 创建一个 ref 用于非受控模式
  const inputRef = useRef(null);

  // 如果组件处于受控模式,使用外部传递的 `value`;否则,使用内部状态 `internalValue`
  const inputValue = isControlled ? value : internalValue;

  // 处理输入变化
  const handleChange = (event) => {
    // 如果是非受控模式,更新内部状态 
    if (!isControlled) {
       setInternalValue(event.target.value);
    }
    
    // 受控和非受控,只要有onChange事件就执行,利于组件使用方使用
    onChange?.(event);
    }
  };

  // 同步 defaultValue 到内部状态
  useEffect(() => {
    if (!isControlled && defaultValue !== undefined) {
      setInternalValue(defaultValue);
    }
  }, [defaultValue, isControlled]);

  return (
    <input
      type="text"
      value={inputValue}
      onChange={handleChange}
      ref={inputRef}
    />
  );
};

export default HybridInput;

使用示例

受控模式

js 复制代码
import React, { useState } from 'react';
import HybridInput from './HybridInput';

const ControlledExample = () => {
  const [value, setValue] = useState('');

  const handleChange = (event) => {
    setValue(event.target.value);
  };

  return (
    <div>
      <HybridInput value={value} onChange={handleChange} />
      <p>Current Value: {value}</p>
    </div>
  );
};

export default ControlledExample;

非受控模式

js 复制代码
import React from 'react';
import HybridInput from './HybridInput';

const UncontrolledExample = () => {
  return (
    <div>
      <HybridInput defaultValue="Initial Value" />
    </div>
  );
};

export default UncontrolledExample;

总结

  • 受控组件 :状态由外部通过 props 控制。通常需要 valueonChange 属性。
  • 非受控组件 :状态由组件内部的 state 控制,通常使用 ref 来访问 DOM 元素的值。

在开发一个React组件时,可以创建一个灵活的组件,能够在受控和非受控两种模式下工作。这样可以在不同的使用场景中提供更大的灵活性。

理解受控和非受控组件的区别有助于你在设计和使用 React 组件时做出更好的选择。希望这些示例和解释对你有所帮助!

相关推荐
不听话坏4 小时前
Ignition篇(下 一) 动态执行前的事情
开发语言·前端·javascript
likeyi074 小时前
require 和 import的区别
开发语言·前端
pany5 小时前
做 AI 友好的开源 Vue3 模板 🌈
前端·vue.js·ai编程
小二·5 小时前
React 19 + Next.js 15 现代前端开发实战:App Router / Server Components / 流式渲染
前端·javascript·react.js
谙忆10246 小时前
前端图片直传对象存储:OSS/S3 预签名 URL、STS 临时凭证与回调校验
前端
CHNE_TAO_EMSM8 小时前
Android studio 打开文件时自动下载源码
前端·javascript·android studio
一孤程8 小时前
Airtest自动化测试第五篇:小程序与Web测试——跨平台自动化全覆盖
前端·自动化测试·小程序·自动化·测试·airtest
IT_陈寒8 小时前
SpringBoot自动配置不是你以为的那样的智能
前端·人工智能·后端
yume_sibai9 小时前
大屏数据可视化 - 边框红绿呼吸灯实现详解
前端·信息可视化·typescript
竹林8189 小时前
从 ethers.js 迁移到 Viem:一个签名验证 Bug 让我彻底放弃旧爱
javascript