Skip to content

函数组件与类组件

学习目标

通过本章学习,你将全面掌握:

  • 函数组件与类组件的定义和使用
  • 两种组件的核心差异
  • 生命周期方法与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" />

组件的特点

  1. 独立性:组件有自己的逻辑和UI
  2. 可复用:同一个组件可以在多处使用
  3. 可组合:小组件可以组合成大组件
  4. 封装性:隐藏内部实现细节

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, useReducerthis.state, this.setState
性能优化React.memo, useMemo, useCallbackshouldComponentUpdate, PureComponent
代码复用自定义Hooks高阶组件, Render Props
错误边界不支持支持
React 19新特性完全支持部分支持
学习曲线平缓陡峭
代码量

6.2 选择建议

优先使用函数组件的场景

  1. 新项目:React 19推荐
  2. 简单组件:无复杂逻辑
  3. 需要Hooks:自定义Hook复用逻辑
  4. 性能优化:利用React Compiler自动优化
  5. Server Components:只能是函数组件
jsx
// 推荐:函数组件
function RecommendedComponent() {
  const data = use(fetchData());
  return <div>{data}</div>;
}

必须使用类组件的场景

  1. 错误边界:只有类组件支持componentDidCatch
  2. 旧项目维护:保持一致性
  3. 特殊生命周期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.jsx

6.4 未来趋势

React团队明确表示:

  1. 函数组件是未来:新特性优先支持函数组件
  2. 类组件仍然支持:不会被移除,但不会有新特性
  3. Hooks是主流:所有新API都基于Hooks
  4. React Compiler:自动优化函数组件
jsx
// React的未来
function FutureComponent() {
  // 自动优化,无需手动memo
  // 自动批量更新
  // 自动处理依赖
  const data = use(fetchData());
  const [state, setState] = useState(0);
  
  return <div>{data} {state}</div>;
}

练习题

基础练习

  1. 将一个简单的类组件转换为函数组件
  2. 实现一个同时支持函数组件和类组件的高阶组件
  3. 创建一个错误边界类组件

进阶练习

  1. 将复杂的类组件(含多个生命周期)迁移为函数组件
  2. 实现自定义Hook,替代类组件的逻辑复用方式
  3. 对比函数组件和类组件的性能差异

高级练习

  1. 使用React 19的use() Hook重写数据获取逻辑
  2. 实现一个支持函数组件和类组件的混合应用
  3. 创建迁移工具,自动转换类组件为函数组件

通过本章学习,你已经全面掌握了函数组件与类组件的所有知识。在React 19时代,函数组件是首选,但理解类组件仍然重要,特别是维护旧代码时。继续学习,成为React专家!