Appearance
React Hooks 入门指南
React Hooks 是 React 16.8 引入的新特性,它允许我们在函数组件中使用状态和其他 React 特性。本文将介绍 React Hooks 的核心概念和使用方法。
1. useState
useState 是最基本的 Hook,用于在函数组件中添加状态。
javascript
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}2. useEffect
useEffect 用于处理副作用,如数据获取、订阅或手动修改 DOM。
javascript
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 类似于 componentDidMount 和 componentDidUpdate
useEffect(() => {
// 更新文档标题
document.title = `You clicked ${count} times`;
// 清理函数,类似于 componentWillUnmount
return () => {
// 执行清理操作
};
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}3. useContext
useContext 用于访问 React 的 Context API。
javascript
import React, { useContext } from 'react';
// 创建 Context
const ThemeContext = React.createContext('light');
function ThemedButton() {
// 使用 Context
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme === 'dark' ? '#333' : '#fff', color: theme === 'dark' ? '#fff' : '#333' }}>
Themed Button
</button>
);
}
function App() {
return (
<ThemeContext.Provider value="dark">
<ThemedButton />
</ThemeContext.Provider>
);
}4. useReducer
useReducer 是 useState 的替代方案,用于处理复杂的状态逻辑。
javascript
import React, { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>
Increment
</button>
<button onClick={() => dispatch({ type: 'decrement' })}>
Decrement
</button>
</div>
);
}5. useCallback
useCallback 用于缓存函数,避免不必要的重新渲染。
javascript
import React, { useState, useCallback } from 'react';
function ChildComponent({ onClick }) {
console.log('ChildComponent rendered');
return <button onClick={onClick}>Click me</button>;
}
function ParentComponent() {
const [count, setCount] = useState(0);
// 缓存 onClick 函数
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []); // 空依赖数组表示只创建一次
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<ChildComponent onClick={handleClick} />
</div>
);
}6. useMemo
useMemo 用于缓存计算结果,避免不必要的重新计算。
javascript
import React, { useState, useMemo } from 'react';
function ExpensiveCalculation({ number }) {
// 缓存计算结果
const result = useMemo(() => {
console.log('Calculating...');
let sum = 0;
for (let i = 0; i < 1000000000; i++) {
sum += i;
}
return sum + number;
}, [number]); // 只有当 number 改变时才重新计算
return <p>Result: {result}</p>;
}
function App() {
const [count, setCount] = useState(0);
const [number, setNumber] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment Count
</button>
<p>Number: {number}</p>
<button onClick={() => setNumber(number + 1)}>
Increment Number
</button>
<ExpensiveCalculation number={number} />
</div>
);
}7. useRef
useRef 用于创建一个可变的 ref 对象,用于访问 DOM 元素或存储任意值。
javascript
import React, { useRef, useState, useEffect } from 'react';
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const [count, setCount] = useState(0);
const focusInput = () => {
// 访问 DOM 元素
inputEl.current.focus();
};
// 存储任意值
const previousCountRef = useRef();
useEffect(() => {
previousCountRef.current = count;
});
const previousCount = previousCountRef.current;
return (
<div>
<input ref={inputEl} type="text" />
<button onClick={focusInput}>Focus the input</button>
<p>Current count: {count}</p>
<p>Previous count: {previousCount}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}8. useImperativeHandle
useImperativeHandle 用于自定义暴露给父组件的实例值。
javascript
import React, { useRef, useImperativeHandle, forwardRef } from 'react';
const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef();
// 自定义暴露给父组件的方法
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
clear: () => {
inputRef.current.value = '';
}
}));
return <input ref={inputRef} {...props} />;
});
function ParentComponent() {
const fancyInputRef = useRef();
return (
<div>
<FancyInput ref={fancyInputRef} />
<button onClick={() => fancyInputRef.current.focus()}>
Focus Input
</button>
<button onClick={() => fancyInputRef.current.clear()}>
Clear Input
</button>
</div>
);
}9. useLayoutEffect
useLayoutEffect 与 useEffect 类似,但它在所有 DOM 变更之后同步执行。
javascript
import React, { useState, useLayoutEffect, useRef } from 'react';
function MeasureExample() {
const [width, setWidth] = useState(0);
const [height, setHeight] = useState(0);
const ref = useRef();
useLayoutEffect(() => {
setWidth(ref.current.offsetWidth);
setHeight(ref.current.offsetHeight);
});
return (
<div>
<div ref={ref} style={{ width: '200px', height: '200px', background: 'red' }}>
Measure me
</div>
<p>Width: {width}px</p>
<p>Height: {height}px</p>
</div>
);
}10. useDebugValue
useDebugValue 用于在 React DevTools 中显示自定义 Hook 的标签。
javascript
import { useDebugValue, useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// 模拟好友状态检查
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
// 假设这是一个真实的 API
// ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
// return () => {
// ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
// };
// 模拟异步操作
setTimeout(() => {
setIsOnline(Math.random() > 0.5);
}, 1000);
}, [friendID]);
// 在 DevTools 中显示自定义标签
useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}
function FriendListItem({ friend }) {
const isOnline = useFriendStatus(friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{friend.name}
</li>
);
}11. 自定义 Hook
我们可以创建自定义 Hook 来复用状态逻辑。
javascript
import { useState, useEffect } from 'react';
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
function handleResize() {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
}
function App() {
const { width, height } = useWindowSize();
return (
<div>
<p>Window width: {width}</p>
<p>Window height: {height}</p>
</div>
);
}12. 最佳实践
- 只在函数顶部调用 Hook:不要在条件语句、循环或嵌套函数中调用 Hook
- 只在 React 函数组件中调用 Hook:不要在普通 JavaScript 函数中调用 Hook
- 使用 ESLint 插件:使用
eslint-plugin-react-hooks来检查 Hook 的使用规则 - 合理使用依赖数组:确保
useEffect、useCallback和useMemo的依赖数组正确 - 避免过度使用 Hook:不要为了使用 Hook 而使用 Hook,保持代码简洁
- 使用自定义 Hook 复用逻辑:将重复的状态逻辑提取到自定义 Hook 中
- 注意性能优化:使用
useCallback和useMemo来避免不必要的重新渲染和计算
13. 常见问题
无限循环
如果 useEffect 的依赖数组不正确,可能会导致无限循环。
javascript
// 错误:count 是依赖项,每次更新都会重新执行
useEffect(() => {
setCount(count + 1);
}, [count]);
// 正确:使用函数式更新
useEffect(() => {
setCount(prevCount => prevCount + 1);
}, []);闭包陷阱
由于闭包的存在,useEffect 可能会捕获到旧的状态值。
javascript
// 错误:count 总是 0
const [count, setCount] = useState(0);
useEffect(() => {
setInterval(() => {
console.log(count); // 总是 0
}, 1000);
}, []);
// 正确:使用 ref 或函数式更新
const countRef = useRef(count);
useEffect(() => {
countRef.current = count;
});
useEffect(() => {
setInterval(() => {
console.log(countRef.current); // 正确的 count 值
}, 1000);
}, []);依赖数组遗漏
如果 useEffect 中使用了某个变量,但没有将其添加到依赖数组中,可能会导致 bug。
javascript
// 错误:遗漏了依赖项 name
const [name, setName] = useState('');
const [greeting, setGreeting] = useState('');
useEffect(() => {
setGreeting(`Hello, ${name}!`);
}, []); // 遗漏了 name
// 正确:添加所有依赖项
useEffect(() => {
setGreeting(`Hello, ${name}!`);
}, [name]);14. 性能优化技巧
- 使用
React.memo:缓存组件,避免不必要的重新渲染 - 使用
useCallback:缓存函数,避免子组件不必要的重新渲染 - 使用
useMemo:缓存计算结果,避免不必要的重新计算 - 使用
useRef:存储不需要触发重新渲染的值 - 合理设置依赖数组:只在必要时重新执行副作用
- 使用
React.lazy和Suspense:实现组件懒加载
15. 总结
React Hooks 为函数组件带来了状态管理、副作用处理等能力,使我们能够编写更加简洁、可维护的代码。通过合理使用各种 Hook,我们可以构建更加复杂、功能强大的 React 应用。
掌握 React Hooks 是现代 React 开发的必备技能,希望本文能够帮助你快速入门 React Hooks,并在实际项目中灵活运用它们。