# React Hooks深度解析
React Hooks是React 16.8引入的新特性,它允许在不编写类组件的情况下使用状态和其他React功能。Hooks解决了React中许多长期存在的问题,并提供了更直观的方式组织组件逻辑。本文将深入探讨React Hooks的原理、应用场景和最佳实践。
# 为什么需要Hooks
在Hooks出现之前,React组件通常有以下痛点:
- 复杂组件难以理解:随着组件变得复杂,相关逻辑分散在不同的生命周期方法中
- 难以复用有状态逻辑:HOC和render props解决方案导致组件嵌套地狱
- 类组件的困扰:this关键字、绑定事件处理器等增加理解门槛
- 优化困难:生命周期方法常包含不相关逻辑,难以分离
Hooks允许我们根据相关性组织代码,而不是基于生命周期方法强制分离。
# 核心Hooks详解
# useState
useState是最基本的Hook,用于在函数组件中添加本地状态:
import React, { useState } from 'react';
function Counter() {
// 声明一个状态变量及其更新函数
const [count, setCount] = useState(0);
return (
<div>
<p>您点击了 {count} 次</p>
<button onClick={() => setCount(count + 1)}>
点击增加
</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>
点击减少
</button>
<button onClick={() => setCount(0)}>
重置
</button>
</div>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
注意事项:
- 更新函数可以接受新值或基于先前值的函数
- 与类组件的
setState不同,更新状态不会自动合并对象 - 可以使用多个
useState声明多个状态变量
# useEffect
useEffect用于处理副作用,如数据获取、订阅、手动DOM修改等:
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// 重置状态
setUser(null);
setLoading(true);
setError(null);
// 获取用户数据
fetch(`https://api.example.com/users/${userId}`)
.then(response => {
if (!response.ok) throw new Error('Failed to fetch');
return response.json();
})
.then(data => {
setUser(data);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
// 清理函数 - 相当于componentWillUnmount
return () => {
// 取消请求或清理资源
console.log('清理副作用');
};
}, [userId]); // 依赖数组 - 仅当userId变化时执行
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error}</div>;
if (!user) return null;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
useEffect执行时机:
| 依赖数组 | 执行时机 |
|---|---|
| 不提供 | 每次渲染后执行 |
空数组[] | 仅在组件挂载和卸载时执行 |
有依赖[a, b] | 在a或b变化时执行 |
# useContext
useContext让组件可以读取context值,实现跨组件共享数据:
import React, { useContext, createContext } from 'react';
// 创建Context
const ThemeContext = createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return <ThemedButton />;
}
function ThemedButton() {
// 使用useContext钩子读取主题值
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme === 'dark' ? '#333' : '#fff',
color: theme === 'dark' ? '#fff' : '#333' }}>
我是{theme}主题按钮
</button>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# useReducer
useReducer是useState的替代方案,适用于复杂的状态逻辑:
import React, { useReducer } from 'react';
// 定义reducer函数
function counterReducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return { count: 0 };
default:
throw new Error(`Unsupported action type: ${action.type}`);
}
}
function Counter() {
// useReducer接收reducer函数和初始状态
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<div>
<p>计数: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>增加</button>
<button onClick={() => dispatch({ type: 'decrement' })}>减少</button>
<button onClick={() => dispatch({ type: 'reset' })}>重置</button>
</div>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# useRef
useRef返回一个可变的ref对象,可用于:
- 访问DOM元素
- 保存任何可变值,但不会触发重新渲染
import React, { useRef, useEffect } from 'react';
function TextInputWithFocus() {
// 创建ref
const inputRef = useRef(null);
// 组件挂载后自动聚焦输入框
useEffect(() => {
inputRef.current.focus();
}, []);
// 使用ref保存不触发重渲染的值
const renderCountRef = useRef(0);
useEffect(() => {
// 更新渲染次数
renderCountRef.current += 1;
console.log(`组件已渲染 ${renderCountRef.current} 次`);
});
return (
<>
<input ref={inputRef} type="text" />
<div>渲染次数: {renderCountRef.current}</div>
</>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# useMemo和useCallback
这两个Hook用于性能优化:
import React, { useState, useMemo, useCallback } from 'react';
function ExpensiveCalculation({ a, b }) {
// useMemo缓存计算结果,仅在依赖变化时重新计算
const result = useMemo(() => {
console.log('进行昂贵计算...');
return a * b * 1000000000;
}, [a, b]);
// useCallback缓存函数引用,避免子组件不必要的重新渲染
const handleClick = useCallback(() => {
console.log(`当前结果: ${result}`);
}, [result]);
return (
<div>
<p>计算结果: {result}</p>
<button onClick={handleClick}>查看结果</button>
</div>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 自定义Hooks
自定义Hooks是复用状态逻辑的最佳方式,不需要添加更多组件到组件树。
import { useState, useEffect } from 'react';
// 自定义Hook: 获取窗口大小
function useWindowSize() {
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
useEffect(() => {
// 处理窗口大小变化
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
// 添加事件监听器
window.addEventListener('resize', handleResize);
// 清理函数
return () => window.removeEventListener('resize', handleResize);
}, []); // 空依赖数组表示仅在挂载和卸载时执行
return windowSize;
}
// 使用自定义Hook
function ResponsiveComponent() {
const { width, height } = useWindowSize();
return (
<div>
<p>窗口宽度: {width}px</p>
<p>窗口高度: {height}px</p>
{width < 768 ? (
<p>移动设备视图</p>
) : (
<p>桌面视图</p>
)}
</div>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 常用自定义Hooks示例
- useLocalStorage - 与localStorage同步的状态
function useLocalStorage(key, initialValue) {
// 读取初始值
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.log(error);
return initialValue;
}
});
// 更新localStorage
const setValue = value => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.log(error);
}
};
return [storedValue, setValue];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- useFetch - 数据获取Hook
function useFetch(url, options) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
setLoading(true);
fetch(url, options)
.then(res => res.json())
.then(data => {
if (isMounted) {
setData(data);
setError(null);
setLoading(false);
}
})
.catch(error => {
if (isMounted) {
setError(error);
setData(null);
setLoading(false);
}
});
return () => {
isMounted = false;
};
}, [url]);
return { data, loading, error };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# Hooks使用规则
Hooks有两条核心规则:
- 只在最顶层使用 Hooks - 不要在循环、条件或嵌套函数中调用Hook
- 只在 React 函数组件或自定义Hook中调用 Hooks - 不要在普通JavaScript函数中调用
这些规则确保Hooks在每次渲染时都按照相同的顺序被调用,这对React识别每个Hook的状态至关重要。
# 从类组件迁移到Hooks
下面是一个类组件到Hooks的迁移示例:
类组件:
class UserProfile extends React.Component {
constructor(props) {
super(props);
this.state = {
user: null,
loading: true,
error: null
};
}
componentDidMount() {
this.fetchUser();
}
componentDidUpdate(prevProps) {
if (prevProps.userId !== this.props.userId) {
this.fetchUser();
}
}
fetchUser() {
this.setState({ loading: true, error: null });
fetch(`https://api.example.com/users/${this.props.userId}`)
.then(res => res.json())
.then(data => {
this.setState({ user: data, loading: false });
})
.catch(error => {
this.setState({ error: error.message, loading: false });
});
}
componentWillUnmount() {
// 清理代码
}
render() {
const { loading, error, user } = this.state;
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error}</div>;
if (!user) return null;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
使用Hooks的函数组件:
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
setLoading(true);
setError(null);
fetch(`https://api.example.com/users/${userId}`)
.then(res => res.json())
.then(data => {
if (isMounted) {
setUser(data);
setLoading(false);
}
})
.catch(error => {
if (isMounted) {
setError(error.message);
setLoading(false);
}
});
return () => {
isMounted = false; // 防止组件卸载后设置状态
};
}, [userId]);
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error}</div>;
if (!user) return null;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# Hooks高级模式与实战技巧
# 状态管理模式
使用useReducer结合Context实现简单的Redux替代方案:
// store.js
import React, { createContext, useReducer, useContext } from 'react';
// 初始状态
const initialState = {
counter: 0,
user: null
};
// Reducer函数
function appReducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, counter: state.counter + 1 };
case 'DECREMENT':
return { ...state, counter: state.counter - 1 };
case 'SET_USER':
return { ...state, user: action.payload };
default:
return state;
}
}
// 创建Context
const StoreContext = createContext();
// Provider组件
export function StoreProvider({ children }) {
const [state, dispatch] = useReducer(appReducer, initialState);
return (
<StoreContext.Provider value={{ state, dispatch }}>
{children}
</StoreContext.Provider>
);
}
// 自定义Hook访问store
export function useStore() {
const context = useContext(StoreContext);
if (!context) {
throw new Error('useStore必须在StoreProvider内使用');
}
return context;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
应用示例:
// App.js
import React from 'react';
import { StoreProvider, useStore } from './store';
function Counter() {
const { state, dispatch } = useStore();
return (
<div>
<p>Count: {state.counter}</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
</div>
);
}
function UserInfo() {
const { state, dispatch } = useStore();
const login = () => {
dispatch({
type: 'SET_USER',
payload: { name: '张三', email: 'zhangsan@example.com' }
});
};
const logout = () => {
dispatch({ type: 'SET_USER', payload: null });
};
return (
<div>
{state.user ? (
<>
<p>当前用户: {state.user.name}</p>
<button onClick={logout}>退出</button>
</>
) : (
<button onClick={login}>登录</button>
)}
</div>
);
}
function App() {
return (
<StoreProvider>
<h1>Hooks状态管理示例</h1>
<Counter />
<UserInfo />
</StoreProvider>
);
}
export default App;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 总结与最佳实践
React Hooks彻底改变了React组件的编写方式,使函数组件能够完全取代类组件。其主要优势包括:
- 更简洁的代码 - 减少样板代码,聚焦业务逻辑
- 更好的关注点分离 - 根据功能而非生命周期组织代码
- 更易复用逻辑 - 自定义Hook允许在组件间共享逻辑
- 更好的类型推断 - 对TypeScript更友好
最佳实践:
- 合理拆分Hooks - 保持每个Hook的职责单一
- 依赖数组谨慎管理 - 确保包含所有必要依赖,使用ESLint插件辅助
- 避免过早优化 - 只在性能成为问题时才使用useMemo和useCallback
- 遵循Hooks命名约定 - 自定义Hook以"use"开头
- 使用函数式更新 - 对于基于先前状态的更新,使用函数形式
Hooks的出现使React更加函数式,并为组件逻辑复用提供了更优雅的解决方案。随着React的发展,Hooks已成为React开发的主流范式。
# 学习资源
- React Hooks官方文档 (opens new window)
- useEffect完全指南 (opens new window) - Dan Abramov著
- React Hooks实战课 (opens new window) - 常用自定义Hooks集合
- React Hooks测试 (opens new window) - 测试自定义Hooks的库