React 构建组件的方式有哪些?区别?
一、是什么
在 React 中,组件是构建用户界面的基本单元。随着 React 版本的迭代,构建组件的方式也在不断演进。从最初的 createClass,到 ES6 类组件,再到如今主流的函数组件 + Hooks,React 提供了多种构建组件的方式来满足不同的场景需求。
理解各种组件构建方式及其特点,有助于在实际项目中选择最合适的方案。
二、函数组件
函数组件是目前 React 官方推荐的主要组件形式。它本质上就是一个接收 props 并返回 JSX 的纯函数。
interface UserCardProps {
name: string;
age: number;
}
function UserCard({ name, age }: UserCardProps) {
return (
<div className="user-card">
<h2>{name}</h2>
<p>年龄:{age}</p>
</div>
);
}
在 React 16.8 引入 Hooks 之后,函数组件获得了管理状态和副作用的能力:
import { useState, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `点击了 ${count} 次`;
}, [count]);
return (
<div>
<p>当前计数:{count}</p>
<button onClick={() => setCount(c => c + 1)}>+1</button>
</div>
);
}
函数组件的优势:
- 代码更简洁,没有
this 绑定问题
- 逻辑复用更方便(自定义 Hooks)
- 更容易进行测试
- 未来 React 优化(如 React Compiler)主要针对函数组件
三、类组件
类组件通过继承 React.Component 或 React.PureComponent 来创建,拥有完整的生命周期方法和内部状态管理能力。
import React, { Component } from 'react';
interface CounterState {
count: number;
}
class Counter extends Component<{}, CounterState> {
state: CounterState = { count: 0 };
handleClick = () => {
this.setState(prev => ({ count: prev.count + 1 }));
};
componentDidMount() {
document.title = `点击了 ${this.state.count} 次`;
}
componentDidUpdate() {
document.title = `点击了 ${this.state.count} 次`;
}
render() {
return (
<div>
<p>当前计数:{this.state.count}</p>
<button onClick={this.handleClick}>+1</button>
</div>
);
}
}
类组件目前仍然可以正常使用,但 React 团队已不再推荐新代码使用类组件。不过 Error Boundary 仍然需要类组件来实现(截至 React 19)。
class ErrorBoundary extends Component<
{ children: React.ReactNode; fallback: React.ReactNode },
{ hasError: boolean }
> {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
console.error('组件错误:', error, info);
}
render() {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}
四、React.memo 包裹组件
React.memo 是一个高阶组件,用于对函数组件进行浅比较的性能优化,当 props 未发生变化时跳过重新渲染。
import { memo } from 'react';
interface ExpensiveListProps {
items: string[];
onItemClick: (item: string) => void;
}
const ExpensiveList = memo(function ExpensiveList({
items,
onItemClick,
}: ExpensiveListProps) {
console.log('ExpensiveList 渲染');
return (
<ul>
{items.map(item => (
<li key={item} onClick={() => onItemClick(item)}>
{item}
</li>
))}
</ul>
);
});
也可以传入自定义比较函数:
const MemoizedComponent = memo(MyComponent, (prevProps, nextProps) => {
return prevProps.id === nextProps.id;
});
在 React 19 中,React Compiler 可以自动对组件进行类似的记忆化优化,减少手动使用 memo 的需求。
五、forwardRef 组件
React.forwardRef 用于将父组件传递的 ref 转发到子组件内部的 DOM 元素或组件实例上。
import { forwardRef, useRef } from 'react';
interface InputProps {
label: string;
placeholder?: string;
}
const LabeledInput = forwardRef<HTMLInputElement, InputProps>(
function LabeledInput({ label, placeholder }, ref) {
return (
<div className="form-field">
<label>{label}</label>
<input ref={ref} placeholder={placeholder} />
</div>
);
}
);
function Form() {
const inputRef = useRef<HTMLInputElement>(null);
const handleFocus = () => {
inputRef.current?.focus();
};
return (
<div>
<LabeledInput ref={inputRef} label="用户名" placeholder="请输入用户名" />
<button onClick={handleFocus}>聚焦输入框</button>
</div>
);
}
在 React 19 中,ref 可以作为普通 prop 传递,不再强制需要 forwardRef 包装:
function LabeledInput({ label, placeholder, ref }: InputProps & { ref?: React.Ref<HTMLInputElement> }) {
return (
<div className="form-field">
<label>{label}</label>
<input ref={ref} placeholder={placeholder} />
</div>
);
}
六、lazy 懒加载组件
React.lazy 配合动态 import() 实现组件的按需加载,常与 Suspense 搭配使用来展示加载状态。
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));
function App() {
return (
<Suspense fallback={<div>加载中...</div>}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
lazy 组件在首次渲染时才会加载对应的代码块,有效减少了应用的初始包体积。
七、PureComponent
PureComponent 是类组件的一个变体,它自动实现了 shouldComponentUpdate,对 props 和 state 进行浅比较来决定是否重新渲染。
import React, { PureComponent } from 'react';
class PureCounter extends PureComponent<{ count: number }> {
render() {
console.log('PureCounter 渲染');
return <span>{this.props.count}</span>;
}
}
它等价于函数组件中的 React.memo,但仅适用于类组件场景。
八、各种方式对比
九、选择建议
在现代 React 开发中,推荐遵循以下原则:
- 默认使用函数组件 + Hooks:绝大多数业务组件都应该使用函数组件
- 需要性能优化时使用
memo:当子组件渲染开销大且 props 变化频率低时
- 组件库开发使用
forwardRef:需要暴露 DOM 引用给消费者时
- 路由级组件使用
lazy:通过代码分割减少首屏加载时间
- Error Boundary 使用类组件:这是目前唯一必须使用类组件的场景
// 现代 React 项目中的典型组件组织
import { lazy, Suspense, memo, forwardRef } from 'react';
const LazyPage = lazy(() => import('./HeavyPage'));
const OptimizedList = memo(function OptimizedList({ data }: { data: Item[] }) {
return <ul>{data.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
});
const CustomInput = forwardRef<HTMLInputElement, InputProps>(
function CustomInput(props, ref) {
return <input ref={ref} {...props} />;
}
);
function App() {
return (
<ErrorBoundary fallback={<p>出错了</p>}>
<Suspense fallback={<p>加载中...</p>}>
<LazyPage />
</Suspense>
</ErrorBoundary>
);
}
十、总结
React 提供了多种构建组件的方式,每种方式都有其特定的使用场景。函数组件 + Hooks 是当前及未来的主流方向,React Compiler(React 19+)的引入将进一步简化性能优化工作。理解每种组件形式的特点和适用场景,能够帮助开发者做出更合理的技术选型决策。