Skip to content

10React组件类型及区别:从基础到进阶的组件分类指南

引言:组件是React的"基本单元"

React的核心思想是"组件化"——将UI拆分为独立、可复用的模块(组件),每个组件专注于解决特定功能。随着React的发展,组件衍生出多种类型,每种类型有其独特的设计目的和使用场景。

本文将系统解析React的四大组件类型:函数组件与类组件、纯组件(PureComponent )、受控组件与非受控组件,从核心区别、使用场景到底层原理,帮你掌握"何时用何种组件"的决策逻辑。

一、函数组件 vs 类组件:两种组件形态的核心差异

React组件最基础的分类是"函数组件"和"类组件"。两者都能描述UI,但在语法、状态管理、生命周期等方面有显著区别。

1.1 语法形态:函数 vs 类

  • 函数组件:用JavaScript函数定义的组件,接收props参数,返回React元素(JSX)。
    示例:

    jsx
    // 函数组件
    const Greeting = (props) => {
      return <h1>Hello, {props.name}!</h1>;
    };
  • 类组件:用ES6类定义的组件,继承React.Component,通过render()方法返回React元素。
    示例:

    jsx
    // 类组件
    class Greeting extends React.Component {
      render() {
        return <h1>Hello, {this.props.name}!</h1>;
      }
    }

1.2 核心差异对比

维度函数组件类组件
状态管理依赖Hooks(useState/useReducer依赖this.statethis.setState()
生命周期依赖useEffect等Hooks模拟有内置生命周期方法(componentDidMount等)
this指向this(函数参数直接获取propsthis,需注意绑定(如事件处理函数)
代码量简洁(无类定义、render方法等模板代码)冗余(需定义类、render方法等)
捕获特性具有"捕获值"特性(每次渲染捕获当前状态)依赖this,可能获取最新状态(非捕获)
适用场景推荐优先使用(React官方趋势)复杂场景(逐渐被Hooks替代)

1.2.1 状态管理:Hooks vs this.state

  • 函数组件:通过useState管理状态,状态更新触发组件重新渲染。

    jsx
    const Counter = () => {
      // 声明状态变量count,初始值0
      const [count, setCount] = useState(0);
    
      return (
        <div>
          <p>Count: {count}</p>
          <button onClick={() => setCount(count + 1)}>加1</button>
        </div>
      );
    };
  • 类组件:通过this.state初始化状态,this.setState()更新状态(异步批量更新)。

    jsx
    class Counter extends React.Component {
      // 初始化状态
      state = { count: 0 };
    
      render() {
        return (
          <div>
            <p>Count: {this.state.count}</p>
            <button onClick={() => this.setState({ count: this.state.count + 1 })}>
              加1
            </button>
          </div>
        );
      }
    }

关键区别

  • 函数组件的状态是"独立快照",每次渲染的count都是当前值;
  • 类组件的this.state是引用,多次setState可能依赖最新状态(需用函数式更新 setState(prev => ({ count: prev.count + 1 })))。

1.2.2 生命周期:useEffect vs 类方法

  • 函数组件:用useEffect模拟生命周期(一个Hook覆盖多个生命周期场景)。

    jsx
    const DataFetcher = () => {
      const [data, setData] = useState(null);
    
      // 模拟componentDidMount(仅挂载时执行)
      useEffect(() => {
        fetch('/api/data').then(res => res.json()).then(setData);
        
        // 模拟componentWillUnmount(清理函数)
        return () => {
          // 取消请求、清除定时器等
        };
      }, []); // 空依赖数组:仅执行一次
    
      return <div>{data ? data.content : 'Loading...'}</div>;
    };
  • 类组件:使用专门的生命周期方法(componentDidMountcomponentWillUnmount等)。

    jsx
    class DataFetcher extends React.Component {
      state = { data: null };
    
      // 组件挂载后执行
      componentDidMount() {
        this.fetchData();
      }
    
      // 组件卸载前执行
      componentWillUnmount() {
        // 清理操作
      }
    
      fetchData = () => {
        fetch('/api/data').then(res => res.json()).then(data => {
          this.setState({ data });
        });
      };
    
      render() {
        return <div>{this.state.data ? this.state.data.content : 'Loading...'}</div>;
      }
    }

关键区别

  • useEffect将"关联的逻辑"(如数据请求+清理)放在一起,而类组件的生命周期方法可能拆分相关逻辑;
  • useEffect在每次渲染后执行(可通过依赖数组控制),类组件的生命周期方法是特定时机触发。

1.2.3 this指向:函数组件的"无this优势"

类组件的this指向实例,事件处理函数需要手动绑定(否则thisundefined),而函数组件没有this问题:

jsx
// 类组件:需绑定this
class Button extends React.Component {
    handleClick() {
        console.log(this.props.text); // 若不绑定,this为undefined
    }

    render() {
        // 方式1:构造函数中绑定
        // this.handleClick = this.handleClick.bind(this);

        // 方式2:箭头函数(推荐)
        return <button onClick={() => this.handleClick()}>
            {this.props.text}
        </button>;
    }
}

// 函数组件:无this,直接使用props
const Button = (props) => {
    const handleClick = () => {
        console.log(props.text); // 直接访问props,无需绑定
    };
    return <button onClick={handleClick}>{props.text}</button>;
};

1.3 如何选择:函数组件优先

React官方在v16.8引入Hooks后,明确推荐优先使用函数组件,原因是:

  • 代码更简洁,减少模板代码(如classrender);
  • 逻辑复用更灵活(Hooks可跨组件复用逻辑,类组件的逻辑复用依赖HOC或Render Props,较复杂);
  • 更容易理解和测试(函数组件是纯函数思想,输入props输出UI)。

类组件仍有其价值(如需要继承PureComponent),但新代码应优先使用函数组件。

二、纯组件(PureComponent):避免不必要的重渲染

纯组件是一种优化性能的组件类型,核心是通过"浅比较"propsstate,决定是否需要重新渲染。

2.1 为什么需要纯组件?

默认情况下,React组件只要propsstate发生变化(即使是无关变化),就会重新渲染(执行render方法)。例如:

jsx
// 子组件:即使props未变化,也会重新渲染
const User = (props) => {
    console.log('User组件重新渲染');
    return <div>{props.name}</div>;
};

// 父组件:count变化时,User会重新渲染(即使name未变)
const Parent = () => {
    const [count, setCount] = useState(0);
    const [name, setName] = useState('张三');

    return (
        <div>
            <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
            <User name={name}/>
        </div>
    );
};

点击按钮时,count变化导致父组件重渲染,子组件User也会跟着重渲染,即使name没有变化——这就是不必要的重渲染 ,纯组件的作用就是避免这种情况。

2.2 纯组件的原理:浅比较(Shallow Comparison)

纯组件通过对比"前后propsstate是否相同",决定是否重渲染:

  • 若相同:不重渲染;
  • 若不同:重渲染。

这里的"相同"指浅比较(而非深比较):

  • 基本类型(string/number/boolean/null/undefined):比较值是否相等(如'a' === 'a');
  • 引用类型(object/array/function):比较引用是否相同(如{a:1}{a:1}引用不同,视为不同)。

2.3 纯组件的实现方式

2.3.1 类组件:React.PureComponent

类组件继承PureComponent后,会自动实现浅比较逻辑:

jsx
// 纯组件:继承PureComponent
class User extends React.PureComponent {
    render() {
        console.log('User组件重新渲染');
        return <div>{this.props.name}</div>;
    }
}

// 父组件:count变化时,User不会重渲染(因为name未变)
const Parent = () => {
    const [count, setCount] = useState(0);
    const [name, setName] = useState('张三');

    return (
        <div>
            <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
            <User name={name}/>
        </div>
    );
};

此时点击按钮,User组件不会重新渲染(因为props.name未变,浅比较通过)。

2.3.2 函数组件:React.memo

函数组件通过React.memo(高阶组件)实现纯组件功能:

jsx
// 纯组件:用React.memo包装函数组件
const User = React.memo((props) => {
    console.log('User组件重新渲染');
    return <div>{props.name}</div>;
});

// 效果同上:count变化时,User不会重渲染

React.memo还可自定义比较逻辑(第二个参数):

jsx
// 自定义比较函数(返回true表示无需重渲染)
const areEqual = (prevProps, nextProps) => {
    return prevProps.name === nextProps.name; // 只比较name字段
};

const User = React.memo((props) => {
    // ...
}, areEqual);

2.4 注意事项:纯组件的"陷阱"

纯组件的浅比较可能失效,导致"该更新时不更新",常见场景:

  1. 引用类型的props/state

    jsx
    // 错误示例:每次渲染创建新对象,导致纯组件误判为变化
    const Parent = () => {
      const user = { name: '张三' }; // 每次渲染创建新对象(引用不同)
      return <User user={user} />; // User是纯组件,会频繁重渲染
    };
    
    // 正确示例:用useMemo缓存对象引用
    const Parent = () => {
      const user = useMemo(() => ({ name: '张三' }), []); // 引用不变
      return <User user={user} />; // 只在依赖变化时重渲染
    };
  2. 传递匿名函数
    匿名函数每次渲染都会创建新引用,导致纯组件误判:

    jsx
    // 错误示例:匿名函数引用变化
    <User onClick={() => console.log('点击')} />
    
    // 正确示例:用useCallback缓存函数
    const handleClick = useCallback(() => {
      console.log('点击');
    }, []);
    <User onClick={handleClick} />

2.5 适用场景:纯组件的最佳实践

纯组件适合渲染逻辑稳定、props/state变化不频繁的组件,尤其是:

  • 列表项组件(如ListItem);
  • 展示型组件(只依赖props渲染,无复杂逻辑);
  • 性能瓶颈明确来自不必要的重渲染的组件。

不适合

  • props/state包含频繁变化的引用类型(如大型数组、对象);
  • 组件内部有复杂的状态逻辑或生命周期(浅比较可能掩盖必要更新)。

三、受控组件 vs 非受控组件:表单元素的两种控制方式

表单元素(如inputselecttextarea)的状态管理有两种模式:受控组件和非受控组件,核心区别是"谁来管理表单数据"。

3.1 受控组件:React状态驱动表单

受控组件:表单元素的值由React的state控制,变化时通过回调函数(如onChange)更新state,从而同步表单值。

简单说:表单值 = state值,表单变化 → 更新state → 表单重新渲染(值更新)。

示例:受控输入框

jsx
const ControlledInput = () => {
    // 用state管理表单值
    const [value, setValue] = useState('');

    // 表单变化时更新state
    const handleChange = (e) => {
        setValue(e.target.value);
    };

    return (
        <input
            type="text"
            value={value} // 表单值由state控制
            onChange={handleChange} // 变化时更新state
        />
    );
};

核心特点

  • 表单值完全由React状态控制("单一数据源");
  • 可实时验证(如输入时检查格式);
  • 可通过state直接获取表单值(无需操作DOM)。

3.2 非受控组件:DOM自身管理表单

非受控组件:表单元素的值由DOM自身管理,React通过ref获取表单值(类似传统HTML表单)。

简单说:表单值 = DOM值,React不控制值的变化,仅在需要时(如提交)通过ref读取。

示例:非受控输入框

jsx
const UncontrolledInput = () => {
    // 创建ref关联DOM元素
    const inputRef = useRef(null);

    // 提交时通过ref获取值
    const handleSubmit = () => {
        const value = inputRef.current.value;
        console.log('输入值:', value);
    };

    return (
        <div>
            <input
                type="text"
                ref={inputRef} // 用ref关联DOM
                defaultValue="初始值" // 初始值(只在首次渲染生效)
            />
            <button onClick={handleSubmit}>提交</button>
        </div>
    );
};

核心特点

  • 表单值由DOM管理,React不跟踪实时变化;
  • 初始值通过defaultValue(输入框)或defaultChecked(复选框)设置;
  • 获取值需要通过ref操作DOM。

3.3 核心区别对比

维度受控组件非受控组件
值的管理者React状态(stateDOM自身
值的获取方式直接从state获取通过ref从DOM获取
实时验证支持(onChange中验证)不支持(需手动监听事件)
初始值设置value(受控,每次渲染生效)defaultValue(非受控,仅首次渲染生效)
适合场景实时交互(表单验证、联动输入)简单场景(一次性提交、文件上传)

3.4 如何选择:场景决定模式

  • 优先用受控组件
    当需要实时处理表单数据(如:

    • 实时验证(如密码强度提示);
    • 表单联动(如选择省份后联动城市列表);
    • 禁用/启用按钮(如输入为空时禁用提交按钮)。
  • 用非受控组件
    当表单逻辑简单,无需实时处理(如:

    • 简单的登录表单(仅提交时验证);
    • 文件上传(input type="file"必须是非受控,因为其值只读);
    • 集成第三方UI库(部分库依赖DOM自身状态)。

四、总结:组件类型的选择指南

React的组件类型本质是"不同场景下的代码组织方式",选择时需结合功能需求、性能优化和开发效率:

  1. 函数组件 vs 类组件
    优先用函数组件+Hooks,简洁且逻辑复用灵活;类组件适合需要PureComponent或维护旧代码的场景。

  2. 纯组件(PureComponent/React.memo
    用于优化不必要的重渲染,尤其适合展示型组件;避免在props/state含频繁变化的引用类型时使用。

  3. 受控组件 vs 非受控组件
    复杂表单(需实时交互)用受控组件;简单场景或文件上传用非受控组件。

理解这些组件类型的核心差异,不仅能写出更高效的代码,更能深入React"组件化"和"声明式编程" 的设计思想——让组件专注于自身职责,通过明确的规则协作,最终构建可维护、可扩展的应用。