深入理解 React Hooks:为何出现、解决了什么问题,以及如何高质量自定义 Hook
一、React为什么要引入Hooks?
React 在 16.8 版本引入 Hooks,是为了解决函数组件和类组件之间的能力不对等问题,以及长期困扰开发者的逻辑复用和代码维护问题。
1. 类组件的复杂性
- 状态逻辑分散在多个生命周期方法中,导致代码难以维护:
jsx
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
componentDidMount() {
console.log('mounted');
}
componentDidUpdate() {
console.log('updated');
}
componentWillUnmount() {
console.log('unmounted');
}
render() {
return
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
{this.state.count}
</button>;
}
}
- 必须理解
this
,逻辑分散、结构冗长、复用困难,提升了使用门槛。
2. 逻辑复用困难
- 高阶组件(HOC)和 render props 虽然解决了复用问题,但导致组件嵌套层级增加:
jsx
<ThemeContext.Consumer>
{theme => (
<UserContext.Consumer>
{user => <Profile user={user} theme={theme} />}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
- 组件嵌套地狱造成调试困难
3.Hooks 提供的能力
- 在函数组件中使用状态、生命周期、副作用等能力;
- 以函数方式复用状态逻辑,无需引入额外组件结构;
- 更好地组织代码和关注点,实现逻辑模块化和组合式开发。
二、React 内置 Hook 作用和使用场景
Hook | 作用 | 典型使用场景 |
---|---|---|
useState |
本地状态管理 | 计数器、表单输入 |
useEffect |
副作用管理 | 数据请求、订阅、DOM 操作 |
useRef |
持久化变量、访问 DOM | 防抖节流、缓存请求、焦点控制 |
useMemo |
计算缓存 | 依赖变化时才重新计算,如表格排序、过滤数据 |
useCallback |
缓存函数 | 组件 props 稳定性、避免重复渲染 |
useContext |
上下文共享 | 跨层级传递全局状态,如主题、用户信息 |
这些 Hook 是开发中使用频率最高的基础能力,掌握它们可以覆盖 90% 的状态逻辑需求。此处简单列举了一下,想要了解hook的具体实现和使用方法,请查阅官网文档或其他文章。
三、自定义hook
1.为什么需要自定义 Hook?
"你是否曾想过:我也可以写一个组件或者工具函数来处理这些逻辑啊,为什么非要用 Hook?答案在于 ------ Hook 能更自然地融入函数组件生命周期,拥有响应式、状态管理和副作用能力,远比组件封装或工具函数更贴合 React 的思想。"
- 提取重复逻辑,避免复制粘贴(例如:分页、搜索、数据加载)
- 分离UI和逻辑,组件更简洁
- 增强组合性和可测试性,便于单元测试和逻辑追踪
- 封装异步数据流、状态流转等复杂流程,更贴近实际业务
示例1:useDebounce防抖hook
ts
function useDebounce<T>(value: T, delay = 300): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
jsx
//应用:搜索输入节流
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 500);
useEffect(() => {
fetch(`/api/search?q=${debouncedQuery}`);
}, [debouncedQuery]);
示例2:usePrevious获取前一个状态
ts
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
//可以用来对比状态变化、实现动画触发逻辑
四、什么时候自定义Hook呢?
判断标准:
- 是否复用:多个组件共享逻辑
- 是否复杂:状态多,副作用频繁
- 是否关注点分离:UI和逻辑混杂
- 是否组合多个Hook:多个Hook之间存在依赖关系
常见的封装场景:
- 数据请求(useRequest, useAxios)
- 弹窗控制(useModal)
- 表单逻辑(useForm)
- 状态缓存(usePersistedState)
- 响应式媒体查询(useMediaQuery)
推荐资源
- React 官方文档:react.dev/learn
- useHooks 中文站:usehooks.com/
- 阿里团队实践文档:developer.aliyun.com/article/125...
- 《自定义 Hooks 使用场景分析》:www.cnblogs.com/qiaozhiming...