三、从 Class 到 Hooks:编程思维的本质转变

103 阅读6分钟

核心思维转变:从"生命周期"到"数据流"

Class 组件的思维模式:生命周期导向

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

  componentDidMount() {
    // 进入时:获取用户和帖子
    fetchUser(this.props.userId).then(user => this.setState({ user }));
    fetchPosts(this.props.userId).then(posts => this.setState({ posts }));
  }

  componentDidUpdate(prevProps) {
    // 更新时:如果 userId 变了,重新获取
    if (prevProps.userId !== this.props.userId) {
      fetchUser(this.props.userId).then(user => this.setState({ user }));
      fetchPosts(this.props.userId).then(posts => this.setState({ posts }));
    }
  }

  componentWillUnmount() {
    // 离开时:清理订阅
    this.cleanup();
  }
}

思考方式:

  • ❓ "组件现在处于哪个生命周期阶段?"
  • ❓ "在这个阶段我应该做什么?"
  • ❓ "我需要在多个生命周期方法中写同样的逻辑"

Hooks 的思维模式:数据同步导向

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    // "让 user 和 userId 保持同步"
    fetchUser(userId).then(setUser);
  }, [userId]);

  useEffect(() => {
    // "让 posts 和 userId 保持同步"
    fetchPosts(userId).then(setPosts);
  }, [userId]);

  useEffect(() => {
    // "建立订阅,并在过期时清理"
    const subscription = subscribeToUpdates(userId);
    return () => subscription.unsubscribe();
  }, [userId]);
}

思考方式:

  • ✅ "这个数据依赖什么?"
  • ✅ "当依赖变化时,数据如何同步?"
  • ✅ "每个 effect 只负责一件事"

编程哲学的四大转变

1️⃣ 从"时间节点"到"因果关系"

Class:时间轴思维

进入组件 → 做AB、C
更新组件 → 检查变化,可能做AB、C
离开组件 → 清理

问题:

  • 相关逻辑分散在不同生命周期
  • 不相关逻辑却挤在同一个生命周期
  • 难以追踪"为什么执行这段代码"

Hooks:因果关系思维

因为 userId 变了 → 所以更新 user
因为 theme 变了 → 所以更新样式
因为需要订阅 → 所以建立连接,离开时断开

优势:

  • 逻辑按照"因果"组织
  • 一个 effect 只做一件事
  • 清晰表达"依赖关系"

2️⃣ 从"组件整体"到"独立关注点"

Class:组件是一个大容器

class Dashboard extends Component {
  state = {
    user: null,
    theme: 'light',
    notifications: [],
    isOnline: false
  };

  componentDidMount() {
    // 混杂了4个不同的关注点
    this.fetchUser();
    this.loadTheme();
    this.subscribeNotifications();
    this.setupOnlineListener();
  }

  componentWillUnmount() {
    // 必须记住清理所有订阅
    this.unsubscribeNotifications();
    this.removeOnlineListener();
  }
}

Hooks:拆分成独立的逻辑单元

function Dashboard() {
  // 关注点1:用户数据
  const user = useUser();

  // 关注点2:主题
  const theme = useTheme();

  // 关注点3:通知
  const notifications = useNotifications();

  // 关注点4:在线状态
  const isOnline = useOnlineStatus();

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

// 每个自定义 Hook 封装完整的逻辑
function useNotifications() {
  const [notifications, setNotifications] = useState([]);

  useEffect(() => {
    const unsub = subscribeNotifications(setNotifications);
    return unsub; // 自动配对的清理
  }, []);

  return notifications;
}

核心理念:按功能分组,而非按生命周期分组


3️⃣ 从"命令式"到"声明式"

Class:告诉 React "怎么做"

class SearchBox extends Component {
  handleInputChange = (e) => {
    const query = e.target.value;
    this.setState({ query });

    // 手动判断和执行
    if (query.length > 2) {
      this.search(query);
    }
  }

  componentDidUpdate(prevProps, prevState) {
    // 手动判断是否需要搜索
    if (this.state.query !== prevState.query && this.state.query.length > 2) {
      this.search(this.state.query);
    }
  }
}

Hooks:告诉 React "什么依赖什么"

function SearchBox() {
  const [query, setQuery] = useState('');

  // 声明:当 query 变化且长度>2 时,执行搜索
  useEffect(() => {
    if (query.length > 2) {
      search(query);
    }
  }, [query]);

  return <input value={query} onChange={e => setQuery(e.target.value)} />;
}

思维转变:

  • Class:我要"控制"每一步
  • Hooks:我"描述"数据关系,React 负责执行

4️⃣ 从"对象和继承"到"函数和组合"

Class:基于对象的思维

class Component {
  // 所有东西都挂在 this 上
  this.state
  this.props
  this.myMethod()

  // 复用通过继承或 HOC
  class MyComponent extends BaseComponent {
    // 继承链...
  }
}

问题:

  • this 指向容易出错
  • 生命周期方法难以复用
  • HOC 嵌套地狱

Hooks:基于函数组合

function MyComponent() {
  // 通过组合多个 Hooks 实现复用
  const auth = useAuth();
  const data = useFetch(url);
  const [localState, setLocalState] = useState();

  // 没有 this,没有继承
  // 一切都是普通的 JavaScript
}

// 自定义 Hook 就是普通函数
function useAuth() {
  const [user, setUser] = useState(null);
  useEffect(() => { /* ... */ }, []);
  return user;
}

核心:用函数组合代替类继承


Hooks 的核心编程模型

1. 状态声明(useState)

// 不是"在生命周期中管理状态"
// 而是"声明组件需要什么状态"
const [count, setCount] = useState(0);

2. 副作用同步(useEffect)

// 不是"在 componentDidMount 中执行"
// 而是"让副作用和数据保持同步"
useEffect(() => {
  document.title = `Count: ${count}`;
}, [count]); // 当 count 变化时,同步 title

3. 性能优化(useMemo/useCallback)

// 不是"手动判断是否需要重新计算"
// 而是"声明依赖,让 React 决定"
const expensiveValue = useMemo(() => {
  return computeExpensive(a, b);
}, [a, b]); // 只有 a 或 b 变化时重新计算

实战对比:完整示例

Class 版本:生命周期思维

class ChatRoom extends Component {
  state = {
    messages: [],
    isTyping: false,
    onlineUsers: []
  };

  componentDidMount() {
    // 混杂三个不同功能
    this.subscribeToMessages(this.props.roomId);
    this.subscribeToTyping(this.props.roomId);
    this.subscribeToOnlineUsers(this.props.roomId);
  }

  componentDidUpdate(prevProps) {
    // 需要判断所有可能的变化
    if (prevProps.roomId !== this.props.roomId) {
      this.unsubscribeAll(prevProps.roomId);
      this.subscribeToMessages(this.props.roomId);
      this.subscribeToTyping(this.props.roomId);
      this.subscribeToOnlineUsers(this.props.roomId);
    }
  }

  componentWillUnmount() {
    // 容易遗漏清理
    this.unsubscribeAll(this.props.roomId);
  }

  subscribeToMessages(roomId) { /* ... */ }
  subscribeToTyping(roomId) { /* ... */ }
  subscribeToOnlineUsers(roomId) { /* ... */ }
  unsubscribeAll(roomId) { /* ... */ }
}

Hooks 版本:数据流思维

function ChatRoom({ roomId }) {
  // 每个功能独立封装
  const messages = useMessages(roomId);
  const isTyping = useTypingStatus(roomId);
  const onlineUsers = useOnlineUsers(roomId);

  return <div>{/* UI */}</div>;
}

// 独立的自定义 Hook
function useMessages(roomId) {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const subscription = subscribeToMessages(roomId, setMessages);
    return () => subscription.unsubscribe();
  }, [roomId]); // 自动处理 roomId 变化

  return messages;
}

function useTypingStatus(roomId) {
  const [isTyping, setIsTyping] = useState(false);

  useEffect(() => {
    const subscription = subscribeToTyping(roomId, setIsTyping);
    return () => subscription.unsubscribe();
  }, [roomId]);

  return isTyping;
}

function useOnlineUsers(roomId) {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    const subscription = subscribeToOnlineUsers(roomId, setUsers);
    return () => subscription.unsubscribe();
  }, [roomId]);

  return users;
}

对比:

  • Class:三个功能混在一起,难以复用
  • Hooks:三个独立单元,可以单独测试和复用

思维练习:如何用 Hooks 思考

❌ 错误的思考方式(生命周期)

"我需要在 componentDidMount 中请求数据..." "然后在 componentDidUpdate 中检查 props 是否变化..." "最后在 componentWillUnmount 中取消请求..."

✅ 正确的思考方式(数据流)

"我需要一个 data 状态" "这个状态依赖 userId" "当 userId 变化时,重新请求数据" "如果组件卸载,取消正在进行的请求"

function useUserData(userId) {
  const [data, setData] = useState(null);

  useEffect(() => {
    let cancelled = false;

    fetchUser(userId).then(user => {
      if (!cancelled) setData(user);
    });

    return () => { cancelled = true; };
  }, [userId]);

  return data;
}

最佳实践:Hooks 编程原则

1. 一个 Effect,一个职责

// ❌ 不好:混杂多个职责
useEffect(() => {
  fetchUser();
  subscribeToNotifications();
  updateTheme();
}, [userId, theme]);

// ✅ 好:每个 effect 只做一件事
useEffect(() => {
  fetchUser(userId);
}, [userId]);

useEffect(() => {
  const unsub = subscribeToNotifications(userId);
  return unsub;
}, [userId]);

useEffect(() => {
  updateTheme(theme);
}, [theme]);

2. 提取自定义 Hook 复用逻辑

// ❌ 不好:逻辑写在组件里
function UserProfile() {
  const [user, setUser] = useState(null);
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]);

  // ... 其他组件也要重复这段逻辑
}

// ✅ 好:提取成自定义 Hook
function useUser(userId) {
  const [user, setUser] = useState(null);
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]);
  return user;
}

function UserProfile() {
  const user = useUser(userId);
  // 简洁!
}

3. 依赖数组要诚实

// ❌ 不好:撒谎的依赖
useEffect(() => {
  fetchData(userId, filter);
}, [userId]); // 少写了 filter

// ✅ 好:完整的依赖
useEffect(() => {
  fetchData(userId, filter);
}, [userId, filter]);

// 或者使用 useCallback 稳定引用
const fetchDataCallback = useCallback(() => {
  fetchData(userId, filter);
}, [userId, filter]);

useEffect(() => {
  fetchDataCallback();
}, [fetchDataCallback]);

总结:编程哲学的转变

维度Class 组件Hooks
思维焦点生命周期(时间)数据流(因果)
代码组织按生命周期分组按功能分组
逻辑复用HOC/Render Props自定义 Hooks
心智模型"在X时刻做Y""Y依赖X"
抽象层次组件整体独立逻辑单元
编程范式面向对象(this)函数式(闭包)

核心顿悟

Hooks 让你从"管理生命周期"转变为"描述数据关系"

  • Class:我要手动控制每个阶段发生什么
  • Hooks:我只需声明数据之间的依赖,React 帮我保持同步

这就像从手动挡(Class)变成自动挡(Hooks):

  • 手动挡:你要考虑何时换挡、离合怎么配合
  • 自动挡:你只需要告诉车要去哪里,车自己处理换挡

进阶阅读

当你掌握了这个思维转变后,可以深入学习:

  1. useEffect 的完整心智模型

    • 每次渲染都有独立的 effect
    • 闭包陷阱和解决方案
    • 何时需要清理函数
  2. 自定义 Hooks 的设计模式

    • 数据获取 Hook
    • 订阅 Hook
    • 状态机 Hook
  3. 性能优化的思维

    • useMemo 不是防止重新计算,而是保持引用相等
    • useCallback 是为了稳定依赖
    • React.memo 与 Hooks 的配合

记住:Hooks 不是语法糖,是编程范式的进化。

从今天开始,停止问"在哪个生命周期做这件事",开始问"这个数据依赖什么"。