React 已经改变,Hooks 也应该如此
现代 React 已经超越了过度使用 useEffect 的阶段。探索 2026 年能够实现更清晰逻辑、更少 bug 和可扩展架构的 Hook 模式。
React Hooks 已经存在多年,但大多数代码库仍然以相同的方式使用它们:一些 useState、一个过载的 useEffect,以及大量不经思考就复制粘贴的模式。我们都经历过。
但 Hooks 从来不是用来简单重写生命周期方法的。它们是一个用于构建更具表现力、模块化架构的设计系统。
随着并发 React(React 18/19 时代)的出现,React 处理数据的方式,特别是异步数据,已经发生了变化。我们现在有了服务端组件、use()、服务端操作、基于框架的数据加载……甚至在客户端组件中也有一些异步功能,具体取决于你的设置。
让我们来看看现代 Hook 模式在今天是什么样子,React 在引导开发者走向何方,以及生态系统持续遇到的陷阱。
useEffect 陷阱:做得太多,太频繁
useEffect 仍然是最常被滥用的 hook。它常常成为不属于这里的逻辑的倾倒场,例如数据获取、派生值,甚至是简单的状态转换。这通常会让组件开始感觉”闹鬼”:它们在奇怪的时候重新运行,或者比应该运行的频率更高。
useEffect(() => {
fetchData();
}, [query]); // 每次查询变化时都会重新运行,即使新值实际上相同
这种困扰主要源于将派生状态和副作用 混在一起,而 React 对它们的处理方式完全不同。
按照 React 的预期使用 effects
React 在这方面的规则出奇地简单:
除了副作用外,其他所有内容都应该在渲染期间派生。
const filteredData = useMemo(() => {
return data.filter((item) => item.includes(query));
}, [data, query]);
当你真正需要一个 effect 时,React 的 useEffectEvent 是你的好帮手。它允许你在 effect 中访问最新的 props/state,而不会破坏你的依赖数组。
const handleSave = useEffectEvent(async () => {
await saveToServer(formData);
});
在触及 useEffect 之前,问问自己:
- 这是由外部因素(网络、DOM、订阅)驱动的吗?
- 还是我可以在渲染期间计算这个?
如果是后者,像 useMemo、useCallback 或框架提供的原语会让你的组件更加健壮。
自定义 Hooks:不仅是可复用性,更是真正的封装
自定义 Hooks 不仅仅是为了减少重复。它们是为了将领域逻辑从组件中抽离出来,让你的 UI 专注于……嗯,UI 本身。
例如,与其用这样的设置代码让组件变得杂乱:
useEffect(() => {
const listener = () => setWidth(window.innerWidth);
window.addEventListener('resize', listener);
return () => window.removeEventListener('resize', listener);
}, []);
你可以将其移到一个 Hook 中:
function useWindowWidth() {
const [width, setWidth] = useState(
typeof window !== 'undefined' ? window.innerWidth : 0,
);
useEffect(() => {
const listener = () => setWidth(window.innerWidth);
window.addEventListener('resize', listener);
return () => window.removeEventListener('change', listener);
}, []);
return width;
}
更清晰。更易于测试。你的组件不再泄露实现细节。
使用 useSyncExternalStore 的基于订阅的状态
React 18 引入了 useSyncExternalStore,它悄悄地解决了订阅、撕裂和高频更新的一大类 bug。
如果你曾经与 matchMedia、滚动位置或在渲染之间行为不一致的第三方存储作斗争,这就是 React 希望你使用的 API。
用于:
- 浏览器 API(
matchMedia、页面可见性、滚动位置) - 外部存储(Redux、Zustand、自定义订阅系统)
- 任何对性能敏感或事件驱动的内容
function useMediaQuery(query) {
return useSyncExternalStore(
(callback) => {
const mql = window.matchMedia(query);
mql.addEventListener('change', callback);
return () => mql.removeEventListener('change', callback);
},
() => window.matchMedia(query).matches,
() => false, // SSR 回退方案
);
}
使用 transitions 和 deferred values 实现更流畅的 UI
如果当用户输入或过滤时你的应用感觉迟钝,React 的并发工具可以提供帮助。这些不是魔法,但它们帮助 React 优先考虑紧急更新而不是昂贵的更新。
const [searchTerm, setSearchTerm] = useState('');
const deferredSearchTerm = useDeferredValue(searchTerm);
const filtered = useMemo(() => {
return data.filter((item) => item.includes(deferredSearchTerm));
}, [data, deferredSearchTerm]);
输入保持响应,而繁重的过滤工作被推后处理。
快速心智模型:
startTransition(() => setState())→ 延迟 状态更新useDeferredValue(value)→ 延迟 派生值
在需要时将它们一起使用,但不要过度。这些不是用于微不足道的计算。
可测试且可调试的 Hooks
现代 React DevTools 让检查自定义 Hooks 变得异常简单。如果你很好地构建你的 Hooks,大部分逻辑变得可以在不渲染实际组件的情况下进行测试。
- 将领域逻辑与 UI 分离
- 尽可能直接测试 Hooks
- 将 provider 逻辑提取到自己的 Hook 中以提高清晰度
function useAuthProvider() {
const [user, setUser] = useState(null);
const login = async (credentials) => {
/* ... */
};
const logout = () => {
/* ... */
};
return { user, login, logout };
}
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const value = useAuthProvider();
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export function useAuth() {
return useContext(AuthContext);
}
下次你必须调试它时,你会感谢自己。
超越 Hooks:迈向数据优先的 React 应用
React 正在转向数据优先的渲染流程,特别是现在服务端组件和基于操作的模式正在成熟。它不是要追求像 Solid.js 那样的细粒度响应性,但 React 正在大力投入异步数据和服务端驱动的 UI。
值得了解的 API:
use()用于渲染期间的异步资源(主要用于服务端组件;通过服务端操作提供有限的客户端组件支持)useEffectEvent用于稳定的 effect 回调useActionState用于工作流式的异步状态- 框架级缓存和数据原语
- 更好的并发渲染工具和 DevTools
方向很明确:React 希望我们减少对”瑞士军刀”式 useEffect 使用的依赖,更多地依赖清晰的渲染驱动的数据流。
围绕派生状态和服务端/客户端边界设计你的 Hooks,可以让你的应用自然地面向未来。
Hooks 作为架构,而非语法
Hooks 不仅仅是比类更好的 API,它们是一种架构模式。
- 保持派生状态在渲染中
- 仅将 effects 用于实际的副作用
- 通过小型、专注的 Hooks 组合逻辑
- 让并发工具平滑异步流程
- 跨越客户端和服务端边界进行思考
React 正在演进。我们的 Hooks 也应该随之演进。
如果你仍然像 2020 年那样编写 Hooks,那也没关系。我们大多数人都是如此。但 React 18+ 为我们提供了更好的工具箱,熟悉这些模式会很快得到回报。现代 React 已经超越了过度使用 useEffect 的阶段。探索 2026 年能够实现更清晰逻辑、更少 bug 和可扩展架构的 Hook 模式。
React Hooks 已经存在多年,但大多数代码库仍然以相同的方式使用它们:一些 useState、一个过载的 useEffect,以及大量不经思考就复制粘贴的模式。我们都经历过。
但 Hooks 从来不是用来简单重写生命周期方法的。它们是一个用于构建更具表现力、模块化架构的设计系统。
随着并发 React(React 18/19 时代)的出现,React 处理数据的方式,特别是异步数据,已经发生了变化。我们现在有了服务端组件、use()、服务端操作、基于框架的数据加载……甚至在客户端组件中也有一些异步功能,具体取决于你的设置。
让我们来看看现代 Hook 模式在今天是什么样子,React 在引导开发者走向何方,以及生态系统持续遇到的陷阱。
useEffect 陷阱:做得太多,太频繁
useEffect 仍然是最常被滥用的 hook。它常常成为不属于这里的逻辑的倾倒场,例如数据获取、派生值,甚至是简单的状态转换。这通常会让组件开始感觉”闹鬼”:它们在奇怪的时候重新运行,或者比应该运行的频率更高。
useEffect(() => {
fetchData();
}, [query]); // 每次查询变化时都会重新运行,即使新值实际上相同
这种困扰主要源于将派生状态和副作用混在一起,而 React 对它们的处理方式完全不同。
按照 React 的预期使用 effects
React 在这方面的规则出奇地简单:
除了副作用外,其他所有内容都应该在渲染期间派生。
const filteredData = useMemo(() => {
return data.filter((item) => item.includes(query));
}, [data, query]);
当你真正需要一个 effect 时,React 的 useEffectEvent 是你的好帮手。它允许你在 effect 中访问最新的 props/state,而不会破坏你的依赖数组。
const handleSave = useEffectEvent(async () => {
await saveToServer(formData);
});
在触及 useEffect 之前,问问自己:
- 这是由外部因素(网络、DOM、订阅)驱动的吗?
- 还是我可以在渲染期间计算这个?
如果是后者,像 useMemo、useCallback 或框架提供的原语会让你的组件更加健壮。
自定义 Hooks:不仅是可复用性,更是真正的封装
自定义 Hooks 不仅仅是为了减少重复。它们是为了将领域逻辑从组件中抽离出来,让你的 UI 专注于……嗯,UI 本身。
例如,与其用这样的设置代码让组件变得杂乱:
useEffect(() => {
const listener = () => setWidth(window.innerWidth);
window.addEventListener('resize', listener);
return () => window.removeEventListener('resize', listener);
}, []);
你可以将其移到一个 Hook 中:
function useWindowWidth() {
const [width, setWidth] = useState(
typeof window !== 'undefined' ? window.innerWidth : 0,
);
useEffect(() => {
const listener = () => setWidth(window.innerWidth);
window.addEventListener('resize', listener);
return () => window.removeEventListener('change', listener);
}, []);
return width;
}
更清晰。更易于测试。你的组件不再泄露实现细节。
使用 useSyncExternalStore 的基于订阅的状态
React 18 引入了 useSyncExternalStore,它悄悄地解决了订阅、撕裂和高频更新的一大类 bug。
如果你曾经与 matchMedia、滚动位置或在渲染之间行为不一致的第三方存储作斗争,这就是 React 希望你使用的 API。
用于:
- 浏览器 API(
matchMedia、页面可见性、滚动位置) - 外部存储(Redux、Zustand、自定义订阅系统)
- 任何对性能敏感或事件驱动的内容
function useMediaQuery(query) {
return useSyncExternalStore(
(callback) => {
const mql = window.matchMedia(query);
mql.addEventListener('change', callback);
return () => mql.removeEventListener('change', callback);
},
() => window.matchMedia(query).matches,
() => false, // SSR 回退方案
);
}
使用 transitions 和 deferred values 实现更流畅的 UI
如果当用户输入或过滤时你的应用感觉迟钝,React 的并发工具可以提供帮助。这些不是魔法,但它们帮助 React 优先考虑紧急更新而不是昂贵的更新。
const [searchTerm, setSearchTerm] = useState('');
const deferredSearchTerm = useDeferredValue(searchTerm);
const filtered = useMemo(() => {
return data.filter((item) => item.includes(deferredSearchTerm));
}, [data, deferredSearchTerm]);
输入保持响应,而繁重的过滤工作被推后处理。
快速心智模型:
startTransition(() => setState())→ 延迟 状态更新useDeferredValue(value)→ 延迟 派生值
在需要时将它们一起使用,但不要过度。这些不是用于微不足道的计算。
可测试且可调试的 Hooks
现代 React DevTools 让检查自定义 Hooks 变得异常简单。如果你很好地构建你的 Hooks,大部分逻辑变得可以在不渲染实际组件的情况下进行测试。
- 将领域逻辑与 UI 分离
- 尽可能直接测试 Hooks
- 将 provider 逻辑提取到自己的 Hook 中以提高清晰度
function useAuthProvider() {
const [user, setUser] = useState(null);
const login = async (credentials) => {
/* ... */
};
const logout = () => {
/* ... */
};
return { user, login, logout };
}
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const value = useAuthProvider();
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export function useAuth() {
return useContext(AuthContext);
}
下次你必须调试它时,你会感谢自己。
超越 Hooks:迈向数据优先的 React 应用
React 正在转向数据优先的渲染流程,特别是现在服务端组件和基于操作的模式正在成熟。它不是要追求像 Solid.js 那样的细粒度响应性,但 React 正在大力投入异步数据和服务端驱动的 UI。
值得了解的 API:
use()用于渲染期间的异步资源(主要用于服务端组件;通过服务端操作提供有限的客户端组件支持)useEffectEvent用于稳定的 effect 回调useActionState用于工作流式的异步状态- 框架级缓存和数据原语
- 更好的并发渲染工具和 DevTools
方向很明确:React 希望我们减少对”瑞士军刀”式 useEffect 使用的依赖,更多地依赖清晰的渲染驱动的数据流。
围绕派生状态和服务端/客户端边界设计你的 Hooks,可以让你的应用自然地面向未来。
Hooks 作为架构,而非语法
Hooks 不仅仅是比类更好的 API,它们是一种架构模式。
- 保持派生状态在渲染中
- 仅将 effects 用于实际的副作用
- 通过小型、专注的 Hooks 组合逻辑
- 让并发工具平滑异步流程
- 跨越客户端和服务端边界进行思考
React 正在演进。我们的 Hooks 也应该随之演进。
如果你仍然像 2020 年那样编写 Hooks,那也没关系。我们大多数人都是如此。但 React 18+ 为我们提供了更好的工具箱,熟悉这些模式会很快得到回报。
本文作者 Matt Smith,吴文俊翻译,转载请注明来源链接:
原文链接: https://allthingssmitty.com/2025/12/01/react-has-changed-your-hooks-should-too/