Appearance
函数组件与类组件
学习目标
通过本章学习,你将全面掌握:
- 函数组件与类组件的定义和使用
- 两种组件的核心差异
- 生命周期方法与Hooks的对应关系
- React 19中的最佳实践
- 组件选择的原则和场景
- 组件迁移策略
第一部分:组件基础
1.1 什么是组件
组件是React应用的基本构建单元,它将UI拆分为独立、可复用的部分。
组件的本质
jsx
// 组件是一个返回React元素的函数或类
// 它接收输入(props),返回应该显示的内容
// 最简单的组件
function Welcome() {
return <h1>Hello, World!</h1>;
}
// 组件可以接收参数
function Greeting(props) {
return <h1>Hello, {props.name}!</h1>;
}
// 使用组件
<Welcome />
<Greeting name="Alice" />组件的特点
- 独立性:组件有自己的逻辑和UI
- 可复用:同一个组件可以在多处使用
- 可组合:小组件可以组合成大组件
- 封装性:隐藏内部实现细节
1.2 函数组件
函数组件是最简单的组件形式,本质上就是一个返回JSX的JavaScript函数。
基本语法
jsx
// 函数声明
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// 函数表达式
const Welcome = function(props) {
return <h1>Hello, {props.name}</h1>;
};
// 箭头函数(推荐)
const Welcome = (props) => {
return <h1>Hello, {props.name}</h1>;
};
// 简化版箭头函数
const Welcome = props => <h1>Hello, {props.name}</h1>;
// 解构props
const Welcome = ({ name }) => <h1>Hello, {name}</h1>;函数组件的特点
jsx
// 1. 简洁
const Button = ({ text, onClick }) => (
<button onClick={onClick}>{text}</button>
);
// 2. 易于理解
const UserCard = ({ user }) => (
<div className="card">
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
// 3. 易于测试
const Sum = ({ a, b }) => <div>{a + b}</div>;
// 测试
expect(Sum({ a: 2, b: 3 }).props.children).toBe(5);
// 4. 支持Hooks(React 16.8+)
const Counter = () => {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
};函数组件的演变历史
jsx
// React 16.8之前:无状态组件
function OldFunctionalComponent(props) {
// 只能接收props,不能有状态
// 不能使用生命周期
return <div>{props.text}</div>;
}
// React 16.8+:有状态函数组件
function ModernFunctionalComponent(props) {
// 可以使用Hooks管理状态
const [state, setState] = useState(initialState);
// 可以使用副作用
useEffect(() => {
// 生命周期逻辑
}, []);
return <div>{state}</div>;
}
// React 19:推荐的主要组件形式
function React19Component(props) {
// 支持所有Hooks
// 支持use()进行数据获取
// 编译器自动优化
const data = use(fetchData());
return <div>{data}</div>;
}1.3 类组件
类组件是基于ES6 class语法的组件,曾经是React的主要组件形式。
基本语法
jsx
import React, { Component } from 'react';
// 类组件必须继承React.Component
class Welcome extends Component {
// render方法是必需的
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
// 使用
<Welcome name="Alice" />类组件的结构
jsx
class MyComponent extends Component {
// 1. 构造函数(可选)
constructor(props) {
super(props);
this.state = {
count: 0
};
// 绑定方法
this.handleClick = this.handleClick.bind(this);
}
// 2. 生命周期方法
componentDidMount() {
console.log('组件已挂载');
}
componentDidUpdate(prevProps, prevState) {
console.log('组件已更新');
}
componentWillUnmount() {
console.log('组件将卸载');
}
// 3. 自定义方法
handleClick() {
this.setState({ count: this.state.count + 1 });
}
// 4. render方法(必需)
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.handleClick}>+1</button>
</div>
);
}
}类组件的特点
jsx
class FeatureDemo extends Component {
// 1. 有内部状态
state = {
data: null,
loading: false
};
// 2. 有生命周期方法
componentDidMount() {
this.fetchData();
}
// 3. 可以定义实例方法
fetchData = async () => {
this.setState({ loading: true });
const data = await api.getData();
this.setState({ data, loading: false });
};
// 4. 使用this访问props和state
render() {
const { loading, data } = this.state;
if (loading) return <Spinner />;
if (!data) return <EmptyState />;
return <DataDisplay data={data} />;
}
}第二部分:核心差异对比
2.1 语法差异
函数组件
jsx
// 简洁的函数式写法
const FunctionComponent = ({ name, age }) => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('Component mounted or updated');
});
const handleClick = () => {
setCount(count + 1);
};
return (
<div>
<p>{name}, {age}, {count}</p>
<button onClick={handleClick}>Click</button>
</div>
);
};类组件
jsx
// 面向对象的类写法
class ClassComponent extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
// 需要手动绑定this
this.handleClick = this.handleClick.bind(this);
}
componentDidMount() {
console.log('Component mounted');
}
componentDidUpdate() {
console.log('Component updated');
}
handleClick() {
this.setState({ count: this.state.count + 1 });
}
render() {
const { name, age } = this.props;
const { count } = this.state;
return (
<div>
<p>{name}, {age}, {count}</p>
<button onClick={this.handleClick}>Click</button>
</div>
);
}
}2.2 状态管理差异
函数组件的状态
jsx
function StateDemo() {
// 使用useState Hook
const [count, setCount] = useState(0);
const [user, setUser] = useState({ name: 'Alice', age: 25 });
const [items, setItems] = useState([]);
// 更新状态
const increment = () => {
setCount(count + 1); // 直接设置新值
// 或使用函数式更新
setCount(prevCount => prevCount + 1);
};
const updateUser = () => {
setUser({ ...user, age: user.age + 1 }); // 需要手动合并
};
const addItem = (item) => {
setItems([...items, item]); // 创建新数组
};
return (
<div>
<p>Count: {count}</p>
<p>User: {user.name}, {user.age}</p>
<button onClick={increment}>Increment</button>
<button onClick={updateUser}>Update User</button>
</div>
);
}类组件的状态
jsx
class StateDemo extends Component {
// 在constructor中初始化state
constructor(props) {
super(props);
this.state = {
count: 0,
user: { name: 'Alice', age: 25 },
items: []
};
}
// 或使用类字段语法
state = {
count: 0,
user: { name: 'Alice', age: 25 },
items: []
};
// 更新状态
increment = () => {
this.setState({ count: this.state.count + 1 });
// 或使用函数式更新
this.setState(prevState => ({
count: prevState.count + 1
}));
};
updateUser = () => {
this.setState({
user: { ...this.state.user, age: this.state.user.age + 1 }
});
};
addItem = (item) => {
this.setState({
items: [...this.state.items, item]
});
};
render() {
const { count, user } = this.state;
return (
<div>
<p>Count: {count}</p>
<p>User: {user.name}, {user.age}</p>
<button onClick={this.increment}>Increment</button>
<button onClick={this.updateUser}>Update User</button>
</div>
);
}
}对比总结
| 特性 | 函数组件 | 类组件 |
|---|---|---|
| 状态声明 | useState(initialValue) | this.state = {} |
| 状态更新 | setState(newValue) | this.setState({}) |
| 多个状态 | 多次调用useState | 合并在一个对象中 |
| 状态合并 | 不自动合并,需手动 | 自动浅合并 |
2.3 生命周期对比
类组件的生命周期
jsx
class LifecycleDemo extends Component {
constructor(props) {
super(props);
console.log('1. Constructor');
this.state = { data: null };
}
static getDerivedStateFromProps(props, state) {
console.log('2. getDerivedStateFromProps');
// 从props派生state
return null;
}
componentDidMount() {
console.log('4. componentDidMount - 挂载后');
// 发起网络请求
// 添加事件监听
}
shouldComponentUpdate(nextProps, nextState) {
console.log('5. shouldComponentUpdate');
// 性能优化,决定是否更新
return true;
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('7. getSnapshotBeforeUpdate');
// 在DOM更新前获取信息
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('8. componentDidUpdate - 更新后');
// DOM已更新
}
componentWillUnmount() {
console.log('9. componentWillUnmount - 卸载前');
// 清理定时器
// 移除事件监听
}
render() {
console.log('3/6. render');
return <div>{this.state.data}</div>;
}
}函数组件的生命周期(Hooks)
jsx
function LifecycleDemo(props) {
const [data, setData] = useState(null);
// componentDidMount
useEffect(() => {
console.log('组件已挂载');
// 发起网络请求
fetchData().then(setData);
// componentWillUnmount
return () => {
console.log('组件将卸载');
// 清理工作
};
}, []); // 空依赖数组 = 只在挂载时执行
// componentDidUpdate (仅当data变化时)
useEffect(() => {
console.log('data已更新:', data);
}, [data]);
// componentDidUpdate (每次渲染后)
useEffect(() => {
console.log('组件已渲染');
});
// getDerivedStateFromProps的替代
const [derivedValue, setDerivedValue] = useState(props.value);
useEffect(() => {
setDerivedValue(props.value);
}, [props.value]);
return <div>{data}</div>;
}生命周期对应关系
jsx
// 完整的对应关系示例
// 类组件
class ClassLifecycle extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
componentDidMount() {
// 挂载后执行
document.title = `Count: ${this.state.count}`;
}
componentDidUpdate(prevProps, prevState) {
// 更新后执行
if (prevState.count !== this.state.count) {
document.title = `Count: ${this.state.count}`;
}
}
componentWillUnmount() {
// 卸载前执行
document.title = 'React App';
}
render() {
return (
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
{this.state.count}
</button>
);
}
}
// 函数组件(等价实现)
function FunctionLifecycle() {
const [count, setCount] = useState(0);
useEffect(() => {
// componentDidMount + componentDidUpdate
document.title = `Count: ${count}`;
// componentWillUnmount
return () => {
document.title = 'React App';
};
}, [count]); // 依赖count,count变化时重新执行
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}2.4 this绑定问题
类组件的this问题
jsx
class ThisProblem extends Component {
state = { count: 0 };
// 问题:this绑定丢失
handleClick() {
// 报错:Cannot read property 'setState' of undefined
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<button onClick={this.handleClick}> {/* this未绑定 */}
{this.state.count}
</button>
);
}
}
// 解决方案1:在constructor中绑定
class Solution1 extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({ count: this.state.count + 1 });
}
render() {
return <button onClick={this.handleClick}>{this.state.count}</button>;
}
}
// 解决方案2:使用箭头函数(推荐)
class Solution2 extends Component {
state = { count: 0 };
handleClick = () => { // 箭头函数自动绑定this
this.setState({ count: this.state.count + 1 });
};
render() {
return <button onClick={this.handleClick}>{this.state.count}</button>;
}
}
// 解决方案3:在render中使用箭头函数(不推荐,性能差)
class Solution3 extends Component {
state = { count: 0 };
handleClick() {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<button onClick={() => this.handleClick()}> {/* 每次渲染创建新函数 */}
{this.state.count}
</button>
);
}
}函数组件没有this问题
jsx
function NoThisProblem() {
const [count, setCount] = useState(0);
// 不需要绑定this
const handleClick = () => {
setCount(count + 1);
};
// 或者直接内联
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}2.5 性能优化差异
类组件的优化
jsx
// 方式1:shouldComponentUpdate
class OptimizedClass extends Component {
shouldComponentUpdate(nextProps, nextState) {
// 手动比较props和state
return this.props.value !== nextProps.value ||
this.state.count !== nextState.count;
}
render() {
return <div>{this.props.value}</div>;
}
}
// 方式2:PureComponent(自动浅比较)
class PureClass extends PureComponent {
// 自动实现shouldComponentUpdate的浅比较
render() {
return <div>{this.props.value}</div>;
}
}函数组件的优化
jsx
// 方式1:React.memo(类似PureComponent)
const MemoizedFunction = React.memo(function MyComponent({ value }) {
return <div>{value}</div>;
});
// 方式2:React.memo with custom comparison
const CustomMemo = React.memo(
function MyComponent({ value }) {
return <div>{value}</div>;
},
(prevProps, nextProps) => {
// 返回true表示props相同,不重新渲染
return prevProps.value === nextProps.value;
}
);
// 方式3:useMemo缓存计算结果
function UseMemoExample({ items }) {
const expensiveValue = useMemo(() => {
return items.reduce((sum, item) => sum + item.value, 0);
}, [items]);
return <div>{expensiveValue}</div>;
}
// 方式4:useCallback缓存函数
function UseCallbackExample() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []); // 依赖为空,函数引用不变
return <ExpensiveChild onClick={handleClick} />;
}第三部分:React 19的最佳实践
3.1 推荐使用函数组件
React 19官方强烈推荐使用函数组件:
jsx
// React 19推荐的写法
function ModernComponent({ userId }) {
// 1. 使用use()获取数据
const user = use(fetchUser(userId));
// 2. 使用Hooks管理状态
const [isEditing, setIsEditing] = useState(false);
// 3. 使用useOptimistic进行乐观更新
const [optimisticUser, updateOptimisticUser] = useOptimistic(
user,
(state, newName) => ({ ...state, name: newName })
);
// 4. 使用useTransition处理低优先级更新
const [isPending, startTransition] = useTransition();
async function handleUpdate(newName) {
updateOptimisticUser(newName);
startTransition(async () => {
await updateUser(userId, newName);
});
}
return (
<div>
<h2>{optimisticUser.name}</h2>
{isPending && <Spinner />}
<button onClick={() => setIsEditing(true)}>编辑</button>
</div>
);
}
// React Compiler会自动优化这个组件
// 无需手动添加useMemo、useCallback等3.2 类组件的迁移
简单迁移
jsx
// 类组件
class OldCounter extends Component {
state = { count: 0 };
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>{this.state.count}</p>
<button onClick={this.increment}>+1</button>
</div>
);
}
}
// 迁移为函数组件
function NewCounter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
return (
<div>
<p>{count}</p>
<button onClick={increment}>+1</button>
</div>
);
}复杂迁移(含生命周期)
jsx
// 类组件
class OldDataFetcher extends Component {
state = {
data: null,
loading: true,
error: null
};
componentDidMount() {
this.fetchData();
}
componentDidUpdate(prevProps) {
if (prevProps.userId !== this.props.userId) {
this.fetchData();
}
}
componentWillUnmount() {
this.abortController.abort();
}
fetchData = async () => {
this.setState({ loading: true });
this.abortController = new AbortController();
try {
const data = await fetch(`/api/users/${this.props.userId}`, {
signal: this.abortController.signal
}).then(r => r.json());
this.setState({ data, loading: false });
} catch (error) {
if (error.name !== 'AbortError') {
this.setState({ error, loading: false });
}
}
};
render() {
const { data, loading, error } = this.state;
if (loading) return <Spinner />;
if (error) return <Error error={error} />;
return <UserProfile data={data} />;
}
}
// 迁移为函数组件
function NewDataFetcher({ userId }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const abortController = new AbortController();
async function fetchData() {
setLoading(true);
try {
const response = await fetch(`/api/users/${userId}`, {
signal: abortController.signal
});
const data = await response.json();
setData(data);
setLoading(false);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err);
setLoading(false);
}
}
}
fetchData();
// 清理函数
return () => {
abortController.abort();
};
}, [userId]); // userId变化时重新获取
if (loading) return <Spinner />;
if (error) return <Error error={error} />;
return <UserProfile data={data} />;
}
// 或使用React 19的use() Hook(更简洁)
function React19DataFetcher({ userId }) {
const data = use(fetchUser(userId)); // 自动处理loading和error
return <UserProfile data={data} />;
}3.3 自定义Hooks提取逻辑
jsx
// 将数据获取逻辑提取为自定义Hook
function useUser(userId) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const abortController = new AbortController();
async function fetchData() {
setLoading(true);
setError(null);
try {
const response = await fetch(`/api/users/${userId}`, {
signal: abortController.signal
});
const data = await response.json();
setData(data);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err);
}
} finally {
setLoading(false);
}
}
fetchData();
return () => abortController.abort();
}, [userId]);
return { data, loading, error };
}
// 使用自定义Hook
function UserProfile({ userId }) {
const { data, loading, error } = useUser(userId);
if (loading) return <Spinner />;
if (error) return <Error error={error} />;
return <div>{data.name}</div>;
}第四部分:高级对比
4.1 错误边界
类组件实现错误边界
jsx
class ErrorBoundary extends Component {
state = {
hasError: false,
error: null,
errorInfo: null
};
static getDerivedStateFromError(error) {
// 更新state,下次渲染显示错误UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 记录错误到日志服务
console.error('Error caught:', error, errorInfo);
this.setState({ error, errorInfo });
}
render() {
if (this.state.hasError) {
return (
<div>
<h1>出错了</h1>
<details>
{this.state.error && this.state.error.toString()}
<br />
{this.state.errorInfo.componentStack}
</details>
</div>
);
}
return this.props.children;
}
}
// 使用
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>函数组件不支持错误边界
jsx
// 函数组件无法使用componentDidCatch
// 必须使用类组件作为错误边界
// 但可以使用第三方库
import { ErrorBoundary } from 'react-error-boundary';
function App() {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onError={(error, errorInfo) => {
console.error('Error:', error, errorInfo);
}}
>
<MyComponent />
</ErrorBoundary>
);
}
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>出错了:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>重试</button>
</div>
);
}4.2 Ref的使用
类组件的Ref
jsx
class ClassRef extends Component {
// 创建ref
inputRef = React.createRef();
componentDidMount() {
// 访问DOM节点
this.inputRef.current.focus();
}
handleClick = () => {
alert(this.inputRef.current.value);
};
render() {
return (
<div>
<input ref={this.inputRef} />
<button onClick={this.handleClick}>获取值</button>
</div>
);
}
}
// 回调Ref
class CallbackRef extends Component {
setInputRef = (element) => {
this.inputElement = element;
};
componentDidMount() {
if (this.inputElement) {
this.inputElement.focus();
}
}
render() {
return <input ref={this.setInputRef} />;
}
}函数组件的Ref
jsx
function FunctionRef() {
// 使用useRef Hook
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
const handleClick = () => {
alert(inputRef.current.value);
};
return (
<div>
<input ref={inputRef} />
<button onClick={handleClick}>获取值</button>
</div>
);
}
// 回调Ref
function CallbackRef() {
const [inputElement, setInputElement] = useState(null);
useEffect(() => {
if (inputElement) {
inputElement.focus();
}
}, [inputElement]);
return <input ref={setInputElement} />;
}forwardRef(函数组件独有)
jsx
// 转发ref到子组件
const FancyInput = forwardRef((props, ref) => {
return <input ref={ref} className="fancy-input" {...props} />;
});
// 使用
function Parent() {
const inputRef = useRef(null);
return (
<div>
<FancyInput ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>
聚焦输入框
</button>
</div>
);
}useImperativeHandle(自定义暴露的ref)
jsx
const CustomInput = forwardRef((props, ref) => {
const inputRef = useRef(null);
// 自定义暴露给父组件的方法
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
clear: () => {
inputRef.current.value = '';
},
getValue: () => {
return inputRef.current.value;
}
}));
return <input ref={inputRef} {...props} />;
});
// 使用
function Parent() {
const inputRef = useRef(null);
return (
<div>
<CustomInput ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>聚焦</button>
<button onClick={() => inputRef.current.clear()}>清空</button>
<button onClick={() => alert(inputRef.current.getValue())}>
获取值
</button>
</div>
);
}4.3 Context的使用
类组件使用Context
jsx
const ThemeContext = React.createContext('light');
// 方式1:contextType(只能使用一个Context)
class ThemedButton extends Component {
static contextType = ThemeContext;
render() {
const theme = this.context;
return <button className={theme}>{this.props.children}</button>;
}
}
// 方式2:Consumer(可以使用多个Context)
class MultiContext extends Component {
render() {
return (
<ThemeContext.Consumer>
{theme => (
<UserContext.Consumer>
{user => (
<div className={theme}>
Welcome, {user.name}!
</div>
)}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
);
}
}函数组件使用Context
jsx
const ThemeContext = React.createContext('light');
// 使用useContext Hook(推荐)
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={theme}>Click me</button>;
}
// 使用多个Context
function MultiContext() {
const theme = useContext(ThemeContext);
const user = useContext(UserContext);
return (
<div className={theme}>
Welcome, {user.name}!
</div>
);
}
// React 19的use() Hook也可以使用Context
function React19Context() {
const theme = use(ThemeContext);
return <button className={theme}>Click me</button>;
}第五部分:实战案例
5.1 完整的表单组件对比
类组件实现
jsx
class FormClass extends Component {
constructor(props) {
super(props);
this.state = {
formData: {
username: '',
email: '',
password: ''
},
errors: {},
submitting: false
};
}
handleChange = (e) => {
const { name, value } = e.target;
this.setState({
formData: {
...this.state.formData,
[name]: value
}
});
};
validate = () => {
const { username, email, password } = this.state.formData;
const errors = {};
if (!username) errors.username = '用户名不能为空';
if (!email) errors.email = '邮箱不能为空';
else if (!/\S+@\S+\.\S+/.test(email)) errors.email = '邮箱格式不正确';
if (!password) errors.password = '密码不能为空';
else if (password.length < 6) errors.password = '密码至少6位';
return errors;
};
handleSubmit = async (e) => {
e.preventDefault();
const errors = this.validate();
this.setState({ errors });
if (Object.keys(errors).length > 0) return;
this.setState({ submitting: true });
try {
await api.register(this.state.formData);
alert('注册成功!');
} catch (error) {
this.setState({ errors: { submit: error.message } });
} finally {
this.setState({ submitting: false });
}
};
render() {
const { formData, errors, submitting } = this.state;
return (
<form onSubmit={this.handleSubmit}>
<div>
<input
name="username"
value={formData.username}
onChange={this.handleChange}
placeholder="用户名"
/>
{errors.username && <span>{errors.username}</span>}
</div>
<div>
<input
name="email"
type="email"
value={formData.email}
onChange={this.handleChange}
placeholder="邮箱"
/>
{errors.email && <span>{errors.email}</span>}
</div>
<div>
<input
name="password"
type="password"
value={formData.password}
onChange={this.handleChange}
placeholder="密码"
/>
{errors.password && <span>{errors.password}</span>}
</div>
{errors.submit && <div>{errors.submit}</div>}
<button type="submit" disabled={submitting}>
{submitting ? '提交中...' : '注册'}
</button>
</form>
);
}
}函数组件实现
jsx
function FormFunction() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: ''
});
const [errors, setErrors] = useState({});
const [submitting, setSubmitting] = useState(false);
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const validate = () => {
const { username, email, password } = formData;
const newErrors = {};
if (!username) newErrors.username = '用户名不能为空';
if (!email) newErrors.email = '邮箱不能为空';
else if (!/\S+@\S+\.\S+/.test(email)) newErrors.email = '邮箱格式不正确';
if (!password) newErrors.password = '密码不能为空';
else if (password.length < 6) newErrors.password = '密码至少6位';
return newErrors;
};
const handleSubmit = async (e) => {
e.preventDefault();
const newErrors = validate();
setErrors(newErrors);
if (Object.keys(newErrors).length > 0) return;
setSubmitting(true);
try {
await api.register(formData);
alert('注册成功!');
} catch (error) {
setErrors({ submit: error.message });
} finally {
setSubmitting(false);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<input
name="username"
value={formData.username}
onChange={handleChange}
placeholder="用户名"
/>
{errors.username && <span>{errors.username}</span>}
</div>
<div>
<input
name="email"
type="email"
value={formData.email}
onChange={handleChange}
placeholder="邮箱"
/>
{errors.email && <span>{errors.email}</span>}
</div>
<div>
<input
name="password"
type="password"
value={formData.password}
onChange={handleChange}
placeholder="密码"
/>
{errors.password && <span>{errors.password}</span>}
</div>
{errors.submit && <div>{errors.submit}</div>}
<button type="submit" disabled={submitting}>
{submitting ? '提交中...' : '注册'}
</button>
</form>
);
}React 19 Server Actions实现(最佳)
jsx
'use client';
import { useActionState } from 'react';
async function registerAction(prevState, formData) {
'use server';
const data = {
username: formData.get('username'),
email: formData.get('email'),
password: formData.get('password')
};
// 验证
const errors = {};
if (!data.username) errors.username = '用户名不能为空';
if (!data.email) errors.email = '邮箱不能为空';
else if (!/\S+@\S+\.\S+/.test(data.email)) errors.email = '邮箱格式不正确';
if (!data.password) errors.password = '密码不能为空';
else if (data.password.length < 6) errors.password = '密码至少6位';
if (Object.keys(errors).length > 0) {
return { errors };
}
// 提交
try {
await api.register(data);
return { success: true };
} catch (error) {
return { errors: { submit: error.message } };
}
}
function FormReact19() {
const [state, formAction, isPending] = useActionState(registerAction, null);
return (
<form action={formAction}>
<div>
<input name="username" placeholder="用户名" />
{state?.errors?.username && <span>{state.errors.username}</span>}
</div>
<div>
<input name="email" type="email" placeholder="邮箱" />
{state?.errors?.email && <span>{state.errors.email}</span>}
</div>
<div>
<input name="password" type="password" placeholder="密码" />
{state?.errors?.password && <span>{state.errors.password}</span>}
</div>
{state?.errors?.submit && <div>{state.errors.submit}</div>}
{state?.success && <div>注册成功!</div>}
<button type="submit" disabled={isPending}>
{isPending ? '提交中...' : '注册'}
</button>
</form>
);
}5.2 数据获取对比
类组件
jsx
class UserProfile extends Component {
state = {
user: null,
loading: true,
error: null
};
abortController = new AbortController();
componentDidMount() {
this.fetchUser();
}
componentDidUpdate(prevProps) {
if (prevProps.userId !== this.props.userId) {
this.fetchUser();
}
}
componentWillUnmount() {
this.abortController.abort();
}
fetchUser = async () => {
this.setState({ loading: true, error: null });
try {
const response = await fetch(`/api/users/${this.props.userId}`, {
signal: this.abortController.signal
});
const user = await response.json();
this.setState({ user, loading: false });
} catch (error) {
if (error.name !== 'AbortError') {
this.setState({ error, loading: false });
}
}
};
render() {
const { user, loading, error } = this.state;
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!user) return null;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
}函数组件(useEffect)
jsx
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const abortController = new AbortController();
async function fetchUser() {
setLoading(true);
setError(null);
try {
const response = await fetch(`/api/users/${userId}`, {
signal: abortController.signal
});
const data = await response.json();
setUser(data);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err);
}
} finally {
setLoading(false);
}
}
fetchUser();
return () => abortController.abort();
}, [userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!user) return null;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}React 19(use Hook)
jsx
import { use, Suspense } from 'react';
function fetchUser(userId) {
return fetch(`/api/users/${userId}`).then(r => r.json());
}
function UserProfile({ userId }) {
// use() Hook自动处理Promise
const user = use(fetchUser(userId));
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
// 使用Suspense处理loading状态
function App({ userId }) {
return (
<Suspense fallback={<div>Loading...</div>}>
<ErrorBoundary fallback={<div>Error occurred</div>}>
<UserProfile userId={userId} />
</ErrorBoundary>
</Suspense>
);
}第六部分:总结与选择指南
6.1 核心对比总结
| 特性 | 函数组件 | 类组件 |
|---|---|---|
| 语法复杂度 | 简单 | 复杂 |
| this绑定 | 无 | 需要 |
| 生命周期 | useEffect等Hooks | 多个生命周期方法 |
| 状态管理 | useState, useReducer | this.state, this.setState |
| 性能优化 | React.memo, useMemo, useCallback | shouldComponentUpdate, PureComponent |
| 代码复用 | 自定义Hooks | 高阶组件, Render Props |
| 错误边界 | 不支持 | 支持 |
| React 19新特性 | 完全支持 | 部分支持 |
| 学习曲线 | 平缓 | 陡峭 |
| 代码量 | 少 | 多 |
6.2 选择建议
优先使用函数组件的场景
- 新项目:React 19推荐
- 简单组件:无复杂逻辑
- 需要Hooks:自定义Hook复用逻辑
- 性能优化:利用React Compiler自动优化
- Server Components:只能是函数组件
jsx
// 推荐:函数组件
function RecommendedComponent() {
const data = use(fetchData());
return <div>{data}</div>;
}必须使用类组件的场景
- 错误边界:只有类组件支持
componentDidCatch - 旧项目维护:保持一致性
- 特殊生命周期:
getSnapshotBeforeUpdate
jsx
// 必须:错误边界
class ErrorBoundary extends Component {
componentDidCatch(error, errorInfo) {
logError(error, errorInfo);
}
render() {
// ...
}
}6.3 迁移策略
渐进式迁移
jsx
// 1. 新组件使用函数组件
function NewFeature() {
// ...
}
// 2. 简单类组件优先迁移
class SimpleOld extends Component {
render() {
return <div>{this.props.text}</div>;
}
}
// 迁移为
const SimpleNew = ({ text }) => <div>{text}</div>;
// 3. 复杂类组件逐步迁移
// - 先提取自定义Hook
// - 再迁移组件主体工具辅助迁移
bash
# 使用react-codemod自动迁移
npx react-codemod class-to-function MyComponent.jsx6.4 未来趋势
React团队明确表示:
- 函数组件是未来:新特性优先支持函数组件
- 类组件仍然支持:不会被移除,但不会有新特性
- Hooks是主流:所有新API都基于Hooks
- React Compiler:自动优化函数组件
jsx
// React的未来
function FutureComponent() {
// 自动优化,无需手动memo
// 自动批量更新
// 自动处理依赖
const data = use(fetchData());
const [state, setState] = useState(0);
return <div>{data} {state}</div>;
}练习题
基础练习
- 将一个简单的类组件转换为函数组件
- 实现一个同时支持函数组件和类组件的高阶组件
- 创建一个错误边界类组件
进阶练习
- 将复杂的类组件(含多个生命周期)迁移为函数组件
- 实现自定义Hook,替代类组件的逻辑复用方式
- 对比函数组件和类组件的性能差异
高级练习
- 使用React 19的use() Hook重写数据获取逻辑
- 实现一个支持函数组件和类组件的混合应用
- 创建迁移工具,自动转换类组件为函数组件
通过本章学习,你已经全面掌握了函数组件与类组件的所有知识。在React 19时代,函数组件是首选,但理解类组件仍然重要,特别是维护旧代码时。继续学习,成为React专家!