组件在不应该重新渲染的情况下频繁重新渲染,导致性能下降。
React.memo包裹函数组件import React, { memo } from 'react';
const MyComponent = memo(function MyComponent({ data }) {
console.log('组件渲染');
return <div>{data}</div>;
});
// 只有当props.data变化时才重新渲染
export default MyComponent;
useMemo缓存计算结果import React, { useMemo } from 'react';
function ExpensiveComponent({ items }) {
const processedItems = useMemo(() => {
console.log('计算处理后的items');
return items.map(item => ({
...item,
processed: heavyComputation(item)
}));
}, [items]); // 只有当items变化时才重新计算
return <List items={processedItems} />;
}
useCallback缓存函数import React, { useCallback } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
// 使用useCallback避免每次渲染都创建新函数
const handleClick = useCallback(() => {
console.log('点击处理');
}, []); // 空依赖数组,函数只创建一次
return (
<div>
<button onClick={() => setCount(count + 1)}>计数: {count}</button>
<ChildComponent onClick={handleClick} />
</div>
);
}
// 不好的做法:所有状态放在一个Context中
const AppContext = createContext();
// 好的做法:按功能拆分Context
const UserContext = createContext();
const ThemeContext = createContext();
const SettingsContext = createContext();
// 组件只订阅需要的Context
function UserProfile() {
const user = useContext(UserContext); // 只有user变化时才重新渲染
return <div>{user.name}</div>;
}
调用setState后立即访问状态,得到的还是旧值。
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
console.log(count); // 这里打印的是旧值,不是更新后的值
};
return <button onClick={handleClick}>计数: {count}</button>;
}
React的状态更新是异步的,调用setState只是安排了更新,并不会立即执行。
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
// 函数式更新可以获取到最新的状态值
setCount(prevCount => {
const newCount = prevCount + 1;
console.log('新值:', newCount);
return newCount;
});
};
return <button onClick={handleClick}>计数: {count}</button>;
}
useEffect监听状态变化function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('count更新为:', count);
}, [count]); // 当count变化时执行
const handleClick = () => {
setCount(count + 1);
};
return <button onClick={handleClick}>计数: {count}</button>;
}
function Counter() {
const [count, setCount] = useState(0);
const countRef = useRef(count);
const handleClick = () => {
const newCount = count + 1;
setCount(newCount);
countRef.current = newCount; // 立即更新ref
console.log('最新值:', countRef.current);
};
return <button onClick={handleClick}>计数: {count}</button>;
}
setState调用可能会被合并。
useEffect无限循环执行,导致应用卡死或请求过多。
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
// 无限循环!每次渲染都会调用setUser,导致重新渲染
useEffect(() => {
fetchUser(userId).then(data => setUser(data));
});
return user ? <div>{user.name}</div> : <div>加载中...</div>;
}
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
// 正确:添加依赖数组,只在userId变化时执行
useEffect(() => {
fetchUser(userId).then(data => setUser(data));
}, [userId]); // ✅ 添加依赖
return user ? <div>{user.name}</div> : <div>加载中...</div>;
}
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
// 使用函数式更新,不需要将setCount添加到依赖
setCount(prev => prev + 1);
}, 1000);
return () => clearInterval(interval);
}, []); // ✅ 空依赖数组,只在挂载时执行一次
return <div>计数: {count}</div>;
}
function ProductList({ products, filter }) {
const [filteredProducts, setFilteredProducts] = useState([]);
// 使用useMemo稳定filter函数
const filterFn = useMemo(() => {
return (product) => product.name.includes(filter);
}, [filter]); // 只有当filter变化时才重新创建
useEffect(() => {
const result = products.filter(filterFn);
setFilteredProducts(result);
}, [products, filterFn]); // ✅ filterFn是稳定的
return <List products={filteredProducts} />;
}
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
const latestMessages = useRef(messages);
useEffect(() => {
latestMessages.current = messages;
});
useEffect(() => {
const connection = createConnection(roomId);
connection.on('message', (msg) => {
// 使用ref访问最新值,不需要将messages添加到依赖
setMessages([...latestMessages.current, msg]);
});
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ 只有roomId是依赖
return <MessageList messages={messages} />;
}
console.log打印effect执行次数和依赖项变化,或使用React Developer Tools的Profiler分析渲染。
渲染包含数千个项目的列表时,应用变得卡顿、响应缓慢。
import { FixedSizeList as List } from 'react-window';
function BigList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
第 {index + 1} 行: {items[index]}
</div>
);
return (
<List
height={400} // 列表高度
width={300} // 列表宽度
itemCount={10000} // 总项目数
itemSize={35} // 每个项目的高度
>
{Row}
</List>
);
}
import React, { Suspense, lazy } from 'react';
// 懒加载列表组件
const LazyList = lazy(() => import('./BigList'));
function App() {
return (
<div>
<Suspense fallback={<div>加载列表中...</div>}>
<LazyList />
</Suspense>
</div>
);
}
function ProductList({ products, searchTerm }) {
// 使用useMemo缓存过滤结果
const filteredProducts = useMemo(() => {
console.log('过滤产品列表');
return products.filter(product =>
product.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [products, searchTerm]);
// 使用useMemo缓存列表项组件
const productItems = useMemo(() => {
return filteredProducts.map(product => (
<ProductItem
key={product.id}
product={product}
/>
));
}, [filteredProducts]);
return <ul>{productItems}</ul>;
}
const ProductItem = memo(function ProductItem({ product }) {
// 只有当product变化时才重新渲染
console.log('渲染产品项:', product.id);
return (
<li className="product-item">
<h3>{product.name}</h3>
<p>价格: ¥{product.price}</p>
<p>库存: {product.stock}</p>
</li>
);
});
// 自定义比较函数(可选)
const areEqual = (prevProps, nextProps) => {
// 只有当id、name、price或stock变化时才重新渲染
return prevProps.product.id === nextProps.product.id &&
prevProps.product.name === nextProps.product.name &&
prevProps.product.price === nextProps.product.price &&
prevProps.product.stock === nextProps.product.stock;
};
export default memo(ProductItem, areEqual);
function PaginatedList() {
const [page, setPage] = useState(1);
const [items, setItems] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const loadPage = useCallback(async (pageNum) => {
setIsLoading(true);
try {
const response = await fetch(`/api/items?page=${pageNum}&limit=20`);
const data = await response.json();
setItems(prev => [...prev, ...data]);
} finally {
setIsLoading(false);
}
}, []);
useEffect(() => {
loadPage(page);
}, [page, loadPage]);
const handleLoadMore = () => {
setPage(prev => prev + 1);
};
return (
<div>
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
{isLoading ? (
<div>加载中...</div>
) : (
<button onClick={handleLoadMore}>
加载更多
</button>
)}
</div>
);
}
直接修改数组或对象,React检测不到变化,组件不会重新渲染。
function TodoList() {
const [todos, setTodos] = useState([{ id: 1, text: '学习React' }]);
const addTodo = (text) => {
// ❌ 错误:直接修改原数组
todos.push({ id: Date.now(), text });
setTodos(todos); // React检测不到变化
// ✅ 正确:创建新数组
// setTodos([...todos, { id: Date.now(), text }]);
};
return <div>{/* ... */}</div>;
}
// 添加元素
setTodos([...todos, newTodo]);
// 删除元素(按索引)
setTodos(todos.filter((_, index) => index !== removeIndex));
// 删除元素(按ID)
setTodos(todos.filter(todo => todo.id !== idToRemove));
// 更新元素
setTodos(todos.map(todo =>
todo.id === idToUpdate ? { ...todo, completed: true } : todo
));
// 插入元素
setTodos([
...todos.slice(0, index),
newTodo,
...todos.slice(index)
]);
// 排序
setTodos([...todos].sort((a, b) => a.name.localeCompare(b.name)));
const [user, setUser] = useState({ name: '张三', age: 25 });
// 更新单个属性
setUser({ ...user, age: 26 });
// 更新多个属性
setUser({ ...user, age: 26, city: '北京' });
// 嵌套对象更新
const [data, setData] = useState({
user: { name: '张三', profile: { age: 25 } }
});
setData({
...data,
user: {
...data.user,
profile: {
...data.user.profile,
age: 26
}
}
});
import produce from 'immer';
function TodoList() {
const [todos, setTodos] = useState([{ id: 1, text: '学习React' }]);
const addTodo = (text) => {
// 使用Immer,可以"直接修改"但实际创建新状态
setTodos(produce(todos, draft => {
draft.push({ id: Date.now(), text });
}));
};
const updateTodo = (id, newText) => {
setTodos(produce(todos, draft => {
const todo = draft.find(t => t.id === id);
if (todo) todo.text = newText;
}));
};
return <div>{/* ... */}</div>;
}
Object.is进行浅比较,只比较引用是否相等,不进行深度比较。
useEffect依赖项复杂时难以调试,不知道哪个依赖导致effect重新执行。
import { useEffect, useRef } from 'react';
// 自定义Hook:记录依赖变化
function useEffectDebugger(effect, dependencies, dependencyNames = []) {
const previousDeps = useRef(dependencies);
useEffect(() => {
if (previousDeps.current) {
// 比较依赖项变化
const changes = dependencies.map((dep, index) => {
const prev = previousDeps.current[index];
const curr = dep;
const name = dependencyNames[index] || `依赖项 ${index}`;
if (prev !== curr) {
return `${name}: ${prev} → ${curr}`;
}
return null;
}).filter(Boolean);
if (changes.length > 0) {
console.log('依赖项变化:', changes);
}
}
previousDeps.current = dependencies;
}, dependencies);
useEffect(effect, dependencies);
}
// 使用示例
function MyComponent({ userId, filter }) {
useEffectDebugger(
() => {
console.log('effect执行');
// ... effect逻辑
},
[userId, filter],
['userId', 'filter'] // 依赖项名称
);
return <div>{/* ... */}</div>;
}
import { useEffect, useRef } from 'react';
// 著名的why-did-you-update Hook
function useWhyDidYouUpdate(name, props) {
const previousProps = useRef();
useEffect(() => {
if (previousProps.current) {
const allKeys = Object.keys({ ...previousProps.current, ...props });
const changes = {};
allKeys.forEach(key => {
if (previousProps.current[key] !== props[key]) {
changes[key] = {
from: previousProps.current[key],
to: props[key]
};
}
});
if (Object.keys(changes).length > 0) {
console.log('[why-did-you-update]', name, changes);
}
}
previousProps.current = props;
});
}
// 使用示例
function MyComponent(props) {
useWhyDidYouUpdate('MyComponent', props);
return <div>{/* ... */}</div>;
}
// 1. 安装React Developer Tools浏览器扩展
// 2. 在Components面板查看组件树和props
// 3. 在Profiler面板记录和分析渲染
// 代码中添加标记帮助调试
import { Profiler } from 'react';
function App() {
const handleRender = (
id, // 发生提交的 Profiler 树的 "id"
phase, // "mount" (如果组件树刚加载) 或者 "update" (如果它重渲染了)
actualDuration, // 本次更新 committed 花费的渲染时间
baseDuration, // 估计不使用 memoization 的情况下渲染整棵子树需要的时间
startTime, // 本次更新中 React 开始渲染的时间
commitTime, // 本次更新中 React committed 的时间
interactions // 属于本次更新的 interactions 的集合
) => {
console.log('Profiler数据:', {
id,
phase,
actualDuration,
baseDuration
});
};
return (
<Profiler id="App" onRender={handleRender}>
<MyComponent />
</Profiler>
);
}
function UserProfile({ userId, options }) {
const { page, sort } = options;
// 在effect外部记录依赖项
console.log('依赖项:', { userId, page, sort });
useEffect(() => {
console.log('effect执行,因为依赖项变化:', { userId, page, sort });
fetchData(userId, { page, sort }).then(data => {
// 数据处理
});
}, [userId, page, sort]);
return <div>{/* ... */}</div>;
}
使用Context传递数据时,所有消费组件都会重新渲染,即使它们只使用Context的一部分数据。
// ❌ 不好的做法:一个Context包含所有数据
const AppContext = createContext();
// ✅ 好的做法:按功能拆分Context
const UserContext = createContext();
const ThemeContext = createContext();
const NotificationContext = createContext();
function App() {
return (
<UserContext.Provider value={user}>
<ThemeContext.Provider value={theme}>
<NotificationContext.Provider value={notifications}>
<MainApp />
</NotificationContext.Provider>
</ThemeContext.Provider>
</UserContext.Provider>
);
}
function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
// 使用useMemo稳定context值
const userContextValue = useMemo(() => ({
user,
setUser,
login: (userData) => setUser(userData),
logout: () => setUser(null)
}), [user]); // 只有当user变化时才重新创建
const themeContextValue = useMemo(() => ({
theme,
setTheme,
toggleTheme: () => setTheme(prev => prev === 'light' ? 'dark' : 'light')
}), [theme]);
return (
<UserContext.Provider value={userContextValue}>
<ThemeContext.Provider value={themeContextValue}>
{children}
</ThemeContext.Provider>
</UserContext.Provider>
);
}
// 创建带选择器的Context
function createContextWithSelector(defaultValue) {
const context = createContext(defaultValue);
const useContextSelector = (selector) => {
const value = useContext(context);
const [selectedValue, setSelectedValue] = useState(() => selector(value));
useLayoutEffect(() => {
const newSelectedValue = selector(value);
if (newSelectedValue !== selectedValue) {
setSelectedValue(newSelectedValue);
}
}, [value, selector, selectedValue]);
return selectedValue;
};
return [context.Provider, useContextSelector];
}
// 使用示例
const [UserProvider, useUserSelector] = createContextWithSelector({});
function App() {
return (
<UserProvider value={userData}>
<UserProfile />
</UserProvider>
);
}
function UserProfile() {
// 只订阅需要的部分数据
const userName = useUserSelector(state => state.name);
const userAvatar = useUserSelector(state => state.avatar);
return (
<div>
<img src={userAvatar} alt={userName} />
<h2>{userName}</h2>
</div>
);
}
const ThemeButton = memo(function ThemeButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
console.log('ThemeButton渲染');
return (
<button
onClick={toggleTheme}
className={`theme-button ${theme}`}
>
当前主题: {theme}
</button>
);
});
// 或者使用useMemo包裹渲染结果
function UserProfile() {
const { user } = useContext(UserContext);
const memoizedContent = useMemo(() => {
console.log('计算用户信息');
return (
<div className="user-profile">
<h2>{user.name}</h2>
<p>邮箱: {user.email}</p>
<p>角色: {user.role}</p>
</div>
);
}, [user.name, user.email, user.role]);
return memoizedContent;
}
在类组件、普通JavaScript函数或条件语句中调用Hook导致错误。
// ❌ 错误:在条件语句中调用Hook
function MyComponent({ shouldFetch }) {
if (shouldFetch) {
const [data, setData] = useState(null); // 错误!
}
return <div>{/* ... */}</div>;
}
// ✅ 正确:始终在顶层调用Hook
function MyComponent({ shouldFetch }) {
const [data, setData] = useState(null); // 正确!
useEffect(() => {
if (shouldFetch) {
fetchData().then(setData);
}
}, [shouldFetch]);
return <div>{/* ... */}</div>;
}
// ❌ 错误:在循环中调用Hook
function UserList({ users }) {
const items = [];
for (let i = 0; i < users.length; i++) {
const [expanded, setExpanded] = useState(false); // 错误!
items.push(
<UserItem
key={users[i].id}
user={users[i]}
expanded={expanded}
onToggle={() => setExpanded(!expanded)}
/>
);
}
return <div>{items}</div>;
}
// ✅ 正确:在子组件中调用Hook
function UserList({ users }) {
return (
<div>
{users.map(user => (
<UserItem key={user.id} user={user} />
))}
</div>
);
}
// 在UserItem组件中使用Hook
function UserItem({ user }) {
const [expanded, setExpanded] = useState(false); // 正确!
return (
<div>
<h3 onClick={() => setExpanded(!expanded)}>{user.name}</h3>
{expanded && <p>{user.email}</p>}
</div>
);
}
// ❌ 错误:在普通函数中调用Hook
function fetchUserData(userId) {
const [data, setData] = useState(null); // 错误!
useEffect(() => {
fetch(`/api/users/${userId}`).then(setData);
}, [userId]);
return data;
}
// ✅ 正确:使用自定义Hook
function useUserData(userId) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`).then(setData);
}, [userId]);
return data;
}
// 在组件中使用自定义Hook
function UserProfile({ userId }) {
const userData = useUserData(userId); // 正确!
if (!userData) return <div>加载中...</div>;
return <div>{userData.name}</div>;
}
// ❌ 错误:在类组件中调用Hook
class MyComponent extends React.Component {
const [count, setCount] = useState(0); // 错误!
render() {
return <div>{count}</div>;
}
}
// ✅ 正确1:将类组件转换为函数组件
function MyComponent() {
const [count, setCount] = useState(0); // 正确!
return <div>{count}</div>;
}
// ✅ 正确2:使用高阶组件包装
function withCounter(WrappedComponent) {
return function WithCounter(props) {
const [count, setCount] = useState(0);
return (
<WrappedComponent
{...props}
count={count}
setCount={setCount}
/>
);
};
}
class MyComponent extends React.Component {
render() {
return <div>{this.props.count}</div>;
}
}
export default withCounter(MyComponent);
eslint-plugin-react-hooks插件自动检测Hook规则违反。
如果你遇到了其他React问题,或者有更好的解决方案,欢迎提交: