乐闻世界logo
搜索文章和话题

React Hook

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