Class 组件的局限性

React Class 组件存在的问题及 Hooks 如何解决

问题

React Class 组件存在哪些问题?为什么 React 团队推出了 Hooks?

解答

1. this 绑定繁琐

Class 组件中事件处理函数需要手动绑定 this,否则会丢失上下文。

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    // 方式1:构造函数中绑定
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState({ count: this.state.count + 1 });
  }

  // 方式2:箭头函数(类字段语法)
  handleReset = () => {
    this.setState({ count: 0 });
  };

  render() {
    return (
      <div>
        <p>{this.state.count}</p>
        <button onClick={this.handleClick}>+1</button>
        {/* 方式3:内联箭头函数(每次渲染创建新函数) */}
        <button onClick={() => this.handleReset()}>Reset</button>
      </div>
    );
  }
}

使用 Hooks 后完全不需要考虑 this

function Counter() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

2. 逻辑复用困难

Class 组件复用状态逻辑需要 HOC 或 render props,容易形成嵌套地狱。

// HOC 方式:多层嵌套
const EnhancedComponent = withRouter(
  withTheme(
    withAuth(
      withLogging(MyComponent)
    )
  )
);

// render props 方式:回调地狱
<ThemeContext.Consumer>
  {theme => (
    <AuthContext.Consumer>
      {auth => (
        <MouseTracker>
          {mouse => (
            <MyComponent theme={theme} auth={auth} mouse={mouse} />
          )}
        </MouseTracker>
      )}
    </AuthContext.Consumer>
  )}
</ThemeContext.Consumer>

Hooks 通过自定义 Hook 实现扁平化复用:

function MyComponent() {
  const theme = useTheme();
  const auth = useAuth();
  const mouse = useMouse();
  // 逻辑清晰,无嵌套
  return <div>...</div>;
}

3. 生命周期分散相关逻辑

同一个功能的代码被拆分到不同生命周期中,难以维护。

class UserProfile extends React.Component {
  state = { user: null, posts: [] };

  componentDidMount() {
    // 订阅用户数据
    this.userSubscription = subscribeToUser(this.props.userId, user => {
      this.setState({ user });
    });
    // 获取文章(同一生命周期混杂不相关逻辑)
    fetchPosts(this.props.userId).then(posts => {
      this.setState({ posts });
    });
  }

  componentDidUpdate(prevProps) {
    // 用户变化时重新订阅
    if (prevProps.userId !== this.props.userId) {
      this.userSubscription.unsubscribe();
      this.userSubscription = subscribeToUser(this.props.userId, user => {
        this.setState({ user });
      });
      fetchPosts(this.props.userId).then(posts => {
        this.setState({ posts });
      });
    }
  }

  componentWillUnmount() {
    // 清理订阅
    this.userSubscription.unsubscribe();
  }

  render() {
    return <div>...</div>;
  }
}

Hooks 按功能组织代码:

function UserProfile({ userId }) {
  // 用户相关逻辑聚合在一起
  const [user, setUser] = useState(null);
  useEffect(() => {
    const subscription = subscribeToUser(userId, setUser);
    return () => subscription.unsubscribe();
  }, [userId]);

  // 文章相关逻辑聚合在一起
  const [posts, setPosts] = useState([]);
  useEffect(() => {
    fetchPosts(userId).then(setPosts);
  }, [userId]);

  return <div>...</div>;
}

4. 样板代码冗余

Class 组件需要大量样板代码:

// Class 组件:约 20 行
class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = { on: false };
    this.toggle = this.toggle.bind(this);
  }

  toggle() {
    this.setState(state => ({ on: !state.on }));
  }

  render() {
    return (
      <button onClick={this.toggle}>
        {this.state.on ? 'ON' : 'OFF'}
      </button>
    );
  }
}

// 函数组件:约 8 行
function Toggle() {
  const [on, setOn] = useState(false);
  return (
    <button onClick={() => setOn(!on)}>
      {on ? 'ON' : 'OFF'}
    </button>
  );
}

5. 难以优化

  • Class 不能很好地压缩(方法名不能被 minify)
  • 热重载不稳定
  • 预编译优化困难(如 component folding)

关键点

  • this 绑定:Class 需要手动绑定,Hooks 无此问题
  • 逻辑复用:HOC/render props 导致嵌套,自定义 Hook 实现扁平复用
  • 代码组织:生命周期按时机划分,Hooks 按功能聚合
  • 代码量:Class 样板代码多,函数组件更简洁
  • 编译优化:函数组件更易被工具链优化