React 高级教程
使用 React 高级组件(HOC)实现的完整项目示例,包含权限控制、数据加载状态处理、性能优化等常见高级功能。创建一个简单的博客系统:
// 项目结构:
src/
|-- components/
| |-- ArticleList.jsx
| |-- Article.jsx
| |-- Header.jsx
| |-- LoginForm.jsx
| |-- UserProfile.jsx
| |-- WithLoading.jsx
| |-- AuthContext.jsx
| |-- WithAuth.jsx
|-- hocs/
| |-- withAuth.js
|-- hooks/
| |-- useFetch.js
| |-- useDebouncedFetch.js
|-- contexts/
| |-- UserContext.js
|-- pages/
| |-- HomePage.jsx
| |-- AdminPage.jsx
| |-- LoginPage.jsx
| |-- UserProfilePage.jsx
| |-- ArticleDetailPage.jsx
|-- App.jsx
|-- index.js
// 首先安装必要依赖:react-router-dom
关键技术点:
1. 创建认证上下文 (AuthContext.jsx)
import { createContext, useContext, useState } from 'react';
const AuthContext = createContext();
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const login = (userData) => {
setUser({ ...userData, role: 'admin' }); // 模拟登录
};
const logout = () => {
setUser(null);
};
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
export const useAuth = () => useContext(AuthContext);
2. 创建高阶组件 (WithAuth.jsx)
import { useNavigate } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
export const withAuth = (WrappedComponent, requiredRole = 'user') => {
return (props) => {
const { user } = useAuth();
const navigate = useNavigate();
if (!user) {
navigate('/login');
return null;
}
if (requiredRole === 'admin' && user.role !== 'admin') {
return <div>无权限访问此页面</div>;
}
return <WrappedComponent {...props} user={user} />;
};
};
3. 加载状态高阶组件 (WithLoading.jsx)
import { useState, useEffect } from 'react';
export const withLoading = (WrappedComponent, fetchData) => {
return (props) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const loadData = async () => {
try {
const result = await fetchData();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
loadData();
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <WrappedComponent {...props} data={data} />;
};
};
4. 自定义 Hook (useFetch.js)
import { useState, useEffect } from 'react';
export const useFetch = (url) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
};
5. 优化列表组件 (OptimizedList.jsx)
import React from 'react';
const OptimizedList = React.memo(({ items, renderItem }) => {
console.log('List re-rendered');
return (
<div style={{ maxHeight: '500px', overflow: 'auto' }}>
{items.map((item, index) => (
<div key={item.id || index}>
{renderItem(item)}
</div>
))}
</div>
);
});
export default OptimizedList;
6. 页面组件示例 (Admin.jsx)
import { withAuth } from '../components/WithAuth';
import { useFetch } from '../hooks/useFetch';
import OptimizedList from '../components/OptimizedList';
const AdminPanel = ({ user }) => {
const { data: posts, loading, error } = useFetch('/api/posts');
if (loading) return <div>Loading posts...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
<h1>欢迎管理员 {user.name}</h1>
<h2>文章管理</h2>
<OptimizedList
items={posts}
renderItem={(post) => (
<div style={{ padding: '10px', borderBottom: '1px solid #ccc' }}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</div>
)}
/>
</div>
);
};
// 使用高阶组件包裹,要求管理员权限
export default withAuth(AdminPanel, 'admin');
7. 主应用组件 (App.jsx)
// App.jsx
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { UserProvider } from './contexts/UserContext';
import HomePage from './pages/HomePage';
import AdminPage from './pages/AdminPage';
import LoginPage from './pages/LoginPage';
import UserProfilePage from './pages/UserProfilePage';
import ArticleDetailPage from './pages/ArticleDetailPage';
const App = () => {
return (
<UserProvider>
<Router>
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/login" component={LoginPage} />
<Route path="/profile" component={UserProfilePage} />
<Route path="/admin" component={AdminPage} />
<Route path="/articles/:id" component={ArticleDetailPage} />
</Switch>
</Router>
</UserProvider>
);
};
export default App;
8. 登录页面示例 (Login.jsx)
import React, { useState, useContext } from 'react';
import { UserContext } from '../contexts/UserContext';
const LoginPage = () => {
const { login } = useContext(UserContext);
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
// 假设我们直接使用固定的用户名和密码登录
if (username === 'admin' && password === 'admin123') {
login({ username, role: 'admin' });
} else {
alert('Invalid credentials');
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="Username"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<button type="submit">Login</button>
</form>
);
};
export default LoginPage;
9. 文章详细页 (ArticleDetailPage.jsx)
展示文章的详细信息,点击文章标题进入。
// pages/ArticleDetailPage.jsx
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { useFetch } from '../hooks/useFetch';
const ArticleDetailPage = () => {
const { id } = useParams();
const { data: article, loading, error } = useFetch(`/api/articles/${id}`);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<h1>{article.title}</h1>
<p>{article.content}</p>
</div>
);
};
export default ArticleDetailPage;
10. 用户个人资料页 (UserProfilePage.jsx)
用户可以更新个人资料。
// pages/UserProfilePage.jsx
import React, { useState, useContext } from 'react';
import { UserContext } from '../contexts/UserContext';
const UserProfilePage = () => {
const { user, logout } = useContext(UserContext);
const [username, setUsername] = useState(user.username);
const handleSave = () => {
// 在这里可以将更新后的用户名保存到后端
console.log('Username updated:', username);
};
return (
<div>
<h1>User Profile</h1>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<button onClick={handleSave}>Save</button>
<button onClick={logout}>Logout</button>
</div>
);
};
export default UserProfilePage;
11. 分页功能 (HomePage.jsx)
文章列表实现分页功能,每页显示一定数量的文章。
// pages/HomePage.jsx
import React, { useState } from 'react';
import { useFetch } from '../hooks/useFetch';
import { ArticleList } from '../components/ArticleList';
const HomePage = () => {
const [page, setPage] = useState(1);
const { data: articles, loading, error } = useFetch(`/api/articles?page=${page}`);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<ArticleList articles={articles} />
<button onClick={() => setPage(page - 1)} disabled={page === 1}>Previous</button>
<button onClick={() => setPage(page + 1)}>Next</button>
</div>
);
};
export default HomePage;
11. 防抖(useDebouncedFetch.js)
用于处理防抖操作,避免频繁请求。
// hooks/useDebouncedFetch.js
import { useState, useEffect } from 'react';
export const useDebouncedFetch = (url, delay) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const timer = setTimeout(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
const result = await response.json();
setData(result);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
};
fetchData();
}, delay);
return () => clearTimeout(timer);
}, [url, delay]);
return { data, loading, error };
};