React Hook
React Hooks 是 React 16.8 版本引入的新特性,它允许在不编写 class 组件的情况下使用 state 和其他 React 特性。Hooks 提供了一种更简洁直观的方式来编写函数组件并复用状态逻辑。

useCallback 和 useMemo 有什么区别?什么场景下使用?## 问题背景
useCallback 和 useMemo 是 React 提供的两个性能优化 Hook,它们看起来很相似,但用途和返回值有本质区别。
## 核心区别
### 语法对比
```jsx
// useCallback:返回函数本身
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
// useMemo:返回函数执行结果
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
```
### 本质区别
| 特性 | useCallback | useMemo |
|------|-------------|---------|
| 返回值 | 函数引用 | 任意值(计算结果) |
| 用途 | 缓存函数 | 缓存计算结果 |
| 类似于 | 函数记忆化 | 值记忆化 |
| 场景 | 避免函数重建 | 避免重复计算 |
## useCallback 详解
### 基本用法
```jsx
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b]
);
```
### 使用场景
#### 1. 传递给子组件避免不必要渲染
```jsx
function Parent({ items }) {
// ❌ 每次渲染都创建新函数,导致 Child 重新渲染
const handleClick = () => {
console.log("clicked");
};
// ✅ 使用 useCallback 缓存函数
const handleClick = useCallback(() => {
console.log("clicked");
}, []);
return <Child onClick={handleClick} items={items} />;
}
// 配合 React.memo 使用
const Child = React.memo(({ onClick, items }) => {
console.log("Child render");
return <button onClick={onClick}>Click</button>;
});
```
#### 2. 作为 useEffect 依赖
```jsx
function UserProfile({ userId }) {
// ❌ fetchUser 每次都是新引用,useEffect 每次都执行
const fetchUser = () => {
api.getUser(userId);
};
// ✅ 使用 useCallback 稳定函数引用
const fetchUser = useCallback(() => {
api.getUser(userId);
}, [userId]);
useEffect(() => {
fetchUser();
}, [fetchUser]);
}
```
## useMemo 详解
### 基本用法
```jsx
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
```
### 使用场景
#### 1. 避免重复计算
```jsx
function ProductList({ products, filter }) {
// ❌ 每次渲染都重新过滤
const filteredProducts = products.filter(p =>
p.name.includes(filter)
);
// ✅ 只在 products 或 filter 变化时重新计算
const filteredProducts = useMemo(() =>
products.filter(p => p.name.includes(filter)),
[products, filter]
);
return <ul>{filteredProducts.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}
```
#### 2. 复杂计算优化
```jsx
function DataTable({ data }) {
// ✅ 大数据量排序只计算一次
const sortedData = useMemo(() => {
console.log("Sorting...");
return [...data].sort((a, b) => a.score - b.score);
}, [data]);
// ✅ 复杂数据转换
const chartData = useMemo(() => {
return data.reduce((acc, item) => {
// 复杂聚合逻辑
return acc;
}, {});
}, [data]);
return <Chart data={chartData} />;
}
```
#### 3. 引用稳定性
```jsx
function Parent({ items }) {
// ❌ 每次渲染创建新对象,导致子组件重渲染
const style = { color: "red" };
// ✅ 保持对象引用稳定
const style = useMemo(() => ({ color: "red" }), []);
return <Child style={style} />;
}
```
## 组合使用
### useCallback 本质上是 useMemo 的语法糖
```jsx
// useCallback 实现
useCallback(fn, deps);
// 等价于
useMemo(() => fn, deps);
```
### 配合使用优化性能
```jsx
function SearchResults({ query, data }) {
// 缓存过滤结果
const results = useMemo(() => {
return data.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
);
}, [data, query]);
// 缓存事件处理函数
const handleItemClick = useCallback((id) => {
console.log("Selected:", id);
}, []);
// 缓存传递给子组件的 props
const listProps = useMemo(() => ({
items: results,
onItemClick: handleItemClick
}), [results, handleItemClick]);
return <ResultList {...listProps} />;
}
```
## 常见误区
### 误区 1:过度使用
```jsx
// ❌ 不必要的优化,简单计算不需要 useMemo
const total = useMemo(() => a + b, [a, b]);
// ✅ 直接计算即可
const total = a + b;
```
### 误区 2:忽略依赖
```jsx
// ❌ 闭包陷阱
const multiplier = 2;
const result = useMemo(() => value * multiplier, [value]);
// ✅ 添加所有依赖
const multiplier = 2;
const result = useMemo(() => value * multiplier, [value, multiplier]);
```
## 最佳实践
1. **先测量,后优化**:使用 React DevTools Profiler 找出性能瓶颈
2. **简单计算不需要缓存**:缓存本身也有开销
3. **配合 React.memo 使用**:确保子组件实现了 shouldComponentUpdate
4. **正确声明依赖**:遵循 ESLint 提示
```jsx
// 好的组合示例
function UserList({ users, onSelect }) {
// 缓存排序结果
const sortedUsers = useMemo(
() => [...users].sort((a, b) => a.name.localeCompare(b.name)),
[users]
);
// 缓存事件处理
const handleSelect = useCallback(
(userId) => onSelect(userId),
[onSelect]
);
return (
<ul>
{sortedUsers.map(user => (
<UserItem
key={user.id}
user={user}
onSelect={handleSelect}
/>
))}
</ul>
);
}
```
服务端 · 3月15日 00:43
如何在 Jest 中测试 React Hooks?如何使用 renderHook 和 act?Jest 提供了多种测试 React Hooks 的方法,主要使用 `@testing-library/react-hooks`:
**1. 测试 useState:**
```javascript
import { renderHook, act } from '@testing-library/react-hooks';
test('useState hook', () => {
const { result } = renderHook(() => useCounter(0));
expect(result.current.count).toBe(0);
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
```
**2. 测试 useEffect:**
```javascript
test('useEffect hook', () => {
const { result } = renderHook(() => useFetch('/api/data'));
expect(result.current.loading).toBe(true);
await act(async () => {
await waitFor(() => !result.current.loading);
});
expect(result.current.data).toBeDefined();
});
```
**3. 测试 useContext:**
```javascript
test('useContext hook', () => {
const wrapper = ({ children }) => (
<ThemeContext.Provider value="dark">
{children}
</ThemeContext.Provider>
);
const { result } = renderHook(() => useTheme(), { wrapper });
expect(result.current.theme).toBe('dark');
});
```
**4. 测试自定义 Hook:**
```javascript
function useCustomHook(initialValue) {
const [value, setValue] = useState(initialValue);
const double = useMemo(() => value * 2, [value]);
return { value, setValue, double };
}
test('custom hook', () => {
const { result } = renderHook(() => useCustomHook(5));
expect(result.current.value).toBe(5);
expect(result.current.double).toBe(10);
act(() => {
result.current.setValue(10);
});
expect(result.current.double).toBe(20);
});
```
**5. 测试异步 Hook:**
```javascript
test('async hook', async () => {
const { result, waitForNextUpdate } = renderHook(() => useAsyncData());
expect(result.current.loading).toBe(true);
await waitForNextUpdate();
expect(result.current.loading).toBe(false);
expect(result.current.data).toBeDefined();
});
```
**6. 测试错误处理:**
```javascript
test('hook error handling', async () => {
const { result, waitForNextUpdate } = renderHook(() => useFetch('/invalid'));
await waitForNextUpdate();
expect(result.current.error).toBeInstanceOf(Error);
});
```
**最佳实践:**
- 使用 `renderHook` 测试 Hook
- 使用 `act` 包装状态更新
- 测试 Hook 的初始状态和更新后的状态
- 测试异步 Hook 时使用 `waitFor` 或 `waitForNextUpdate`
- 测试错误边界情况
- 保持测试简单,专注于 Hook 的行为
服务端 · 2月21日 15:57