核心思维转变:从"生命周期"到"数据流"
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:时间轴思维
进入组件 → 做A、B、C
更新组件 → 检查变化,可能做A、B、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):
- 手动挡:你要考虑何时换挡、离合怎么配合
- 自动挡:你只需要告诉车要去哪里,车自己处理换挡
进阶阅读
当你掌握了这个思维转变后,可以深入学习:
-
useEffect 的完整心智模型
- 每次渲染都有独立的 effect
- 闭包陷阱和解决方案
- 何时需要清理函数
-
自定义 Hooks 的设计模式
- 数据获取 Hook
- 订阅 Hook
- 状态机 Hook
-
性能优化的思维
- useMemo 不是防止重新计算,而是保持引用相等
- useCallback 是为了稳定依赖
- React.memo 与 Hooks 的配合
记住:Hooks 不是语法糖,是编程范式的进化。
从今天开始,停止问"在哪个生命周期做这件事",开始问"这个数据依赖什么"。