Material-UI 性能优化 #
概述 #
性能优化是构建高质量应用的重要环节。本章节介绍 MUI 应用的各种性能优化技巧。
包大小优化 #
Tree Shaking #
MUI 支持 Tree Shaking,只打包使用的组件:
jsx
import { Button, TextField } from '@mui/material';
按需导入图标 #
jsx
import DeleteIcon from '@mui/icons-material/Delete';
import AddIcon from '@mui/icons-material/Add';
使用 Babel 插件 #
javascript
// babel.config.js
module.exports = {
plugins: [
[
'babel-plugin-import',
{
libraryName: '@mui/material',
libraryDirectory: '',
camel2DashComponentName: false,
},
'core',
],
[
'babel-plugin-import',
{
libraryName: '@mui/icons-material',
libraryDirectory: '',
camel2DashComponentName: false,
},
'icons',
],
],
};
渲染优化 #
使用 React.memo #
jsx
import { memo } from 'react';
const UserCard = memo(function UserCard({ user }) {
return (
<Card>
<CardContent>
<Typography>{user.name}</Typography>
</CardContent>
</Card>
);
});
避免内联样式 #
jsx
const styles = {
container: {
p: 2,
bgcolor: 'primary.main',
},
};
function MyComponent() {
return <Box sx={styles.container}>内容</Box>;
}
使用 useMemo #
jsx
import { useMemo } from 'react';
function UserList({ users, filter }) {
const filteredUsers = useMemo(() => {
return users.filter((user) => user.name.includes(filter));
}, [users, filter]);
return (
<List>
{filteredUsers.map((user) => (
<ListItem key={user.id}>{user.name}</ListItem>
))}
</List>
);
}
使用 useCallback #
jsx
import { useCallback } from 'react';
function UserList({ users }) {
const handleClick = useCallback((userId) => {
console.log('User clicked:', userId);
}, []);
return (
<List>
{users.map((user) => (
<ListItemButton key={user.id} onClick={() => handleClick(user.id)}>
{user.name}
</ListItemButton>
))}
</List>
);
}
样式性能 #
使用 styled 代替 sx #
对于频繁渲染的组件,使用 styled 更高效:
jsx
import { styled } from '@mui/material/styles';
const StyledCard = styled(Card)(({ theme }) => ({
padding: theme.spacing(2),
borderRadius: theme.shape.borderRadius * 2,
}));
function MyComponent() {
return <StyledCard>内容</StyledCard>;
}
缓存样式 #
jsx
import { styled } from '@mui/material/styles';
const useStyles = styled('div')(({ theme }) => ({
padding: theme.spacing(2),
backgroundColor: theme.palette.background.paper,
}));
列表优化 #
虚拟化长列表 #
jsx
import { FixedSizeList } from 'react-window';
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<ListItem style={style}>
<ListItemText primary={items[index].name} />
</ListItem>
);
return (
<FixedSizeList
height={400}
width="100%"
itemCount={items.length}
itemSize={46}
>
{Row}
</FixedSizeList>
);
}
分页加载 #
jsx
import { useState } from 'react';
function PaginatedList({ items }) {
const [page, setPage] = useState(0);
const itemsPerPage = 10;
const displayedItems = items.slice(
page * itemsPerPage,
(page + 1) * itemsPerPage
);
return (
<>
<List>
{displayedItems.map((item) => (
<ListItem key={item.id}>{item.name}</ListItem>
))}
</List>
<Button onClick={() => setPage(page + 1)}>加载更多</Button>
</>
);
}
图片优化 #
懒加载图片 #
jsx
import { useState, useEffect, useRef } from 'react';
function LazyImage({ src, alt, ...props }) {
const [isLoaded, setIsLoaded] = useState(false);
const imgRef = useRef();
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setIsLoaded(true);
observer.disconnect();
}
});
});
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => observer.disconnect();
}, []);
return (
<Box ref={imgRef}>
{isLoaded ? (
<Box component="img" src={src} alt={alt} {...props} />
) : (
<Skeleton variant="rectangular" width="100%" height={200} />
)}
</Box>
);
}
响应式图片 #
jsx
function ResponsiveImage({ src, alt }) {
return (
<Box
component="img"
src={src}
alt={alt}
sx={{
width: '100%',
height: 'auto',
maxWidth: { xs: '100%', md: '50%' },
}}
/>
);
}
代码分割 #
路由级分割 #
jsx
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { CircularProgress } from '@mui/material';
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));
function App() {
return (
<BrowserRouter>
<Suspense
fallback={
<Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}>
<CircularProgress />
</Box>
}
>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
组件级分割 #
jsx
import { lazy, Suspense } from 'react';
const HeavyChart = lazy(() => import('./HeavyChart'));
function Dashboard() {
return (
<Box>
<Typography variant="h4">仪表板</Typography>
<Suspense fallback={<Skeleton variant="rectangular" height={300} />}>
<HeavyChart />
</Suspense>
</Box>
);
}
主题优化 #
避免重复创建主题 #
jsx
import { useMemo } from 'react';
import { createTheme, ThemeProvider } from '@mui/material/styles';
function App() {
const theme = useMemo(
() =>
createTheme({
palette: {
primary: { main: '#1976d2' },
},
}),
[]
);
return (
<ThemeProvider theme={theme}>
<MyApp />
</ThemeProvider>
);
}
使用 CSS 变量 #
jsx
const theme = createTheme({
cssVariables: true,
});
状态管理优化 #
避免不必要的状态更新 #
jsx
import { useState, useCallback } from 'react';
function SearchInput({ onSearch }) {
const [value, setValue] = useState('');
const debouncedSearch = useCallback(
debounce((searchValue) => {
onSearch(searchValue);
}, 300),
[onSearch]
);
const handleChange = (e) => {
const newValue = e.target.value;
setValue(newValue);
debouncedSearch(newValue);
};
return (
<TextField
value={value}
onChange={handleChange}
placeholder="搜索..."
/>
);
}
使用 Context 优化 #
jsx
import { createContext, useContext, useMemo } from 'react';
const UserContext = createContext();
function UserProvider({ children }) {
const [user, setUser] = useState(null);
const value = useMemo(() => ({ user, setUser }), [user]);
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
}
function useUser() {
return useContext(UserContext);
}
性能监控 #
React DevTools Profiler #
jsx
import { Profiler } from 'react';
function onRenderCallback(
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime
) {
console.log(`${id} ${phase} took ${actualDuration}ms`);
}
function App() {
return (
<Profiler id="App" onRender={onRenderCallback}>
<MyComponent />
</Profiler>
);
}
使用 useTransition #
jsx
import { useState, useTransition } from 'react';
function SearchResults() {
const [isPending, startTransition] = useTransition();
const [results, setResults] = useState([]);
const handleSearch = (query) => {
startTransition(() => {
const filtered = heavyFiltering(query);
setResults(filtered);
});
};
return (
<Box>
<TextField onChange={(e) => handleSearch(e.target.value)} />
{isPending && <CircularProgress />}
<List>
{results.map((item) => (
<ListItem key={item.id}>{item.name}</ListItem>
))}
</List>
</Box>
);
}
最佳实践总结 #
| 优化项 | 方法 |
|---|---|
| 包大小 | Tree Shaking、按需导入 |
| 渲染 | memo、useMemo、useCallback |
| 样式 | styled 代替 sx、缓存样式 |
| 列表 | 虚拟化、分页 |
| 图片 | 懒加载、响应式 |
| 代码 | 代码分割、懒加载 |
| 主题 | 避免重复创建、CSS 变量 |
下一步 #
继续学习 最佳实践,了解企业级开发的规范和技巧!
最后更新:2026-03-28