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.state 和this.setState() |
生命周期 | 依赖useEffect 等Hooks模拟 | 有内置生命周期方法(componentDidMount 等) |
this指向 | 无this (函数参数直接获取props ) | 有this ,需注意绑定(如事件处理函数) |
代码量 | 简洁(无类定义、render 方法等模板代码) | 冗余(需定义类、render 方法等) |
捕获特性 | 具有"捕获值"特性(每次渲染捕获当前状态) | 依赖this ,可能获取最新状态(非捕获) |
适用场景 | 推荐优先使用(React官方趋势) | 复杂场景(逐渐被Hooks替代) |
1.2.1 状态管理:Hooks vs this.state
函数组件:通过
useState
管理状态,状态更新触发组件重新渲染。jsxconst 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()
更新状态(异步批量更新)。jsxclass 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覆盖多个生命周期场景)。jsxconst 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>; };
类组件:使用专门的生命周期方法(
componentDidMount
、componentWillUnmount
等)。jsxclass 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
指向实例,事件处理函数需要手动绑定(否则this
为undefined
),而函数组件没有this
问题:
// 类组件:需绑定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后,明确推荐优先使用函数组件,原因是:
- 代码更简洁,减少模板代码(如
class
、render
); - 逻辑复用更灵活(Hooks可跨组件复用逻辑,类组件的逻辑复用依赖HOC或Render Props,较复杂);
- 更容易理解和测试(函数组件是纯函数思想,输入
props
输出UI)。
类组件仍有其价值(如需要继承PureComponent
),但新代码应优先使用函数组件。
二、纯组件(PureComponent
):避免不必要的重渲染
纯组件是一种优化性能的组件类型,核心是通过"浅比较"props
和state
,决定是否需要重新渲染。
2.1 为什么需要纯组件?
默认情况下,React组件只要props
或state
发生变化(即使是无关变化),就会重新渲染(执行render
方法)。例如:
// 子组件:即使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)
纯组件通过对比"前后props
和state
是否相同",决定是否重渲染:
- 若相同:不重渲染;
- 若不同:重渲染。
这里的"相同"指浅比较(而非深比较):
- 基本类型(string/number/boolean/null/undefined):比较值是否相等(如
'a' === 'a'
); - 引用类型(object/array/function):比较引用是否相同(如
{a:1}
与{a:1}
引用不同,视为不同)。
2.3 纯组件的实现方式
2.3.1 类组件:React.PureComponent
类组件继承PureComponent
后,会自动实现浅比较逻辑:
// 纯组件:继承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
(高阶组件)实现纯组件功能:
// 纯组件:用React.memo包装函数组件
const User = React.memo((props) => {
console.log('User组件重新渲染');
return <div>{props.name}</div>;
});
// 效果同上:count变化时,User不会重渲染
React.memo
还可自定义比较逻辑(第二个参数):
// 自定义比较函数(返回true表示无需重渲染)
const areEqual = (prevProps, nextProps) => {
return prevProps.name === nextProps.name; // 只比较name字段
};
const User = React.memo((props) => {
// ...
}, areEqual);
2.4 注意事项:纯组件的"陷阱"
纯组件的浅比较可能失效,导致"该更新时不更新",常见场景:
引用类型的
props
/state
:jsx// 错误示例:每次渲染创建新对象,导致纯组件误判为变化 const Parent = () => { const user = { name: '张三' }; // 每次渲染创建新对象(引用不同) return <User user={user} />; // User是纯组件,会频繁重渲染 }; // 正确示例:用useMemo缓存对象引用 const Parent = () => { const user = useMemo(() => ({ name: '张三' }), []); // 引用不变 return <User user={user} />; // 只在依赖变化时重渲染 };
传递匿名函数:
匿名函数每次渲染都会创建新引用,导致纯组件误判:jsx// 错误示例:匿名函数引用变化 <User onClick={() => console.log('点击')} /> // 正确示例:用useCallback缓存函数 const handleClick = useCallback(() => { console.log('点击'); }, []); <User onClick={handleClick} />
2.5 适用场景:纯组件的最佳实践
纯组件适合渲染逻辑稳定、props
/state
变化不频繁的组件,尤其是:
- 列表项组件(如
ListItem
); - 展示型组件(只依赖
props
渲染,无复杂逻辑); - 性能瓶颈明确来自不必要的重渲染的组件。
不适合:
props
/state
包含频繁变化的引用类型(如大型数组、对象);- 组件内部有复杂的状态逻辑或生命周期(浅比较可能掩盖必要更新)。
三、受控组件 vs 非受控组件:表单元素的两种控制方式
表单元素(如input
、select
、textarea
)的状态管理有两种模式:受控组件和非受控组件,核心区别是"谁来管理表单数据"。
3.1 受控组件:React状态驱动表单
受控组件:表单元素的值由React的state
控制,变化时通过回调函数(如onChange
)更新state
,从而同步表单值。
简单说:表单值 = state值,表单变化 → 更新state → 表单重新渲染(值更新)。
示例:受控输入框
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
读取。
示例:非受控输入框
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状态(state ) | DOM自身 |
值的获取方式 | 直接从state 获取 | 通过ref 从DOM获取 |
实时验证 | 支持(onChange 中验证) | 不支持(需手动监听事件) |
初始值设置 | value (受控,每次渲染生效) | defaultValue (非受控,仅首次渲染生效) |
适合场景 | 实时交互(表单验证、联动输入) | 简单场景(一次性提交、文件上传) |
3.4 如何选择:场景决定模式
优先用受控组件:
当需要实时处理表单数据(如:- 实时验证(如密码强度提示);
- 表单联动(如选择省份后联动城市列表);
- 禁用/启用按钮(如输入为空时禁用提交按钮)。
用非受控组件:
当表单逻辑简单,无需实时处理(如:- 简单的登录表单(仅提交时验证);
- 文件上传(
input type="file"
必须是非受控,因为其值只读); - 集成第三方UI库(部分库依赖DOM自身状态)。
四、总结:组件类型的选择指南
React的组件类型本质是"不同场景下的代码组织方式",选择时需结合功能需求、性能优化和开发效率:
函数组件 vs 类组件:
优先用函数组件+Hooks,简洁且逻辑复用灵活;类组件适合需要PureComponent
或维护旧代码的场景。纯组件(
PureComponent
/React.memo
):
用于优化不必要的重渲染,尤其适合展示型组件;避免在props
/state
含频繁变化的引用类型时使用。受控组件 vs 非受控组件:
复杂表单(需实时交互)用受控组件;简单场景或文件上传用非受控组件。
理解这些组件类型的核心差异,不仅能写出更高效的代码,更能深入React"组件化"和"声明式编程" 的设计思想——让组件专注于自身职责,通过明确的规则协作,最终构建可维护、可扩展的应用。