注: 本 React 项目基于 Create React App 构建,实现了用户认证与路由鉴权、数据的增删改查,并深入介绍了 React Hook 的封装与组件间通信等核心技术。该项目内容丰富,非常适合 React 初学者进行学习和实践。以下是对项目的部分介绍,详见源码。
项目地址 gitee: https://gitee.com/zcr-1/react-jike.git
项目登录账号 :13800000002
项目登录密码 :246810
一、项目准备
1.创建项目
- npx create-react-app my-app
2.启动项目
- npm start
3.调整项目结构目录
-src
-apis 项目接口函数
-assets 项目资源文件,比如,图片等
-components 通用组件
-pages 页面组件
-store 集中状态管理
-utils 工具,比如,token、axios 的封装等
-App.js 根组件
-index.css 全局样式
-index.js 项目入口
4.删除无用的文件
- 根目录留下 App.js、index.js、index.css,其他文件删除
5.安装依赖
- npm i sass -D
- npm i antd
- npm i normalize.css
6.配置别名路径
-
安装 craco 工具包 npm i @craco/craco -D
-
在根目录添加 craco.config.js 配置文件
const path = require('path')
module.exports = {
// webpack 配置
webpack: {
// 配置别名
alias: {
// 约定:使用 @ 表示 src 文件所在路径
'@': path.resolve(__dirname, 'src')
}
}
}
- 修改 package.json 文件配置
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "react-scripts eject"
}
7.VScode 配置提示
- 在项目根目录创建 jsconfig.json 配置文件
- 在配置文件中添加以下配置
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
}
}
二、配置路由
- 安装依赖 npm i react-router-dom
- 准备基础组件 Layout Login
- 配置路由
- 代码实现
pages/Layout/index.js pages/Login/index.js
const Layout = () => {
return <div>this is layout</div>
}
export default Layout
router/index.js
import { createBrowserRouter } from 'react-router-dom';
import Layout from '@/pages/Layout';
import Login from '@/pages/Login';
// 配置
const router = createBrowserRouter([
{
path: '/',
element: <Layout/>,
},
{
path: '/Login',
element:<Login/>
}
])
export default router;
index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.scss'
import router from './router'
import { RouterProvider } from 'react-router-dom'
ReactDOM.createRoot(document.getElementById('root')).render(
<RouterProvider router={router} />
)
三、封装 request 工具
1.安装 axios 到项目 npm i axios
2.创建 utils/request.js 文件
3.创建 axios 实例,配置 baseURL,请求拦截器,响应拦截器
import axios from 'axios'
const request = axios.create({
baseURL: 'http://geek.itheima.net/v1_0',
timeout: 5000
})
// 添加请求拦截器
request.interceptors.request.use((config) => {
return config
}, (error) => {
return Promise.reject(error)
})
// 添加响应拦截器
request.interceptors.response.use((response) => {
return response.data
}, (error) => {
return Promise.reject(error)
})
export { request }
4.在 utils/index.js 中,统一导出request
import { request } from './request'
export { request }
四、封装请求接口 API
1.根据封装的 request.js 封装接口
2.代码演示
import { request } from "@/utils"
// POST 带参请求
export const logininApi = (data) => {
return request({
url:'/authorizations',
method:'POST',
data
})
}
// GET 带参请求
export const getArticleAPI = (data) => {
return request({
url: '/mp/articles',
method: 'GET',
params: data
})
}
// GET 无参请求 参数可省略
export const getArticleById = (id) => {
return request({
url: `/mp/articles/${id}`,
})
}
// DELETE 请求
export const delArticleAPI = (id) => {
return request({
url: `/mp/articles/${id}`,
method: 'DELETE',
})
}
// PUT 请求
export const updateArticleAPI = (data) => {
return request({
url: `/mp/articles/${data.id}?draft_false`,
method: 'PUT',
data
})
}
五、使用 Redux 管理 token
1.安装 redux 相关工具包 npm i react-redux @reduxjs/toolkit
2.配置 Redux
- 创建 user.js 文件
import { createSlice } from "@reduxjs/toolkit";
import { logininApi,fetchUserInfoApi } from "@/apis/user"
import {
request,
getToken,
setToken as _setToken,
removeToken,
} from "@/utils";
const userStore = createSlice({
name: 'user',
initialState: {
token: getToken() || '',
userInfo: {}
},
// 同步修改方法
reducers: {
setToken(state, action) {
state.token = action.payload
_setToken(action.payload)
},
setUserInfo(state, action) {
state.userInfo = action.payload
},
clearnUserInfo(state) {
state.token = ''
state.userInfo = {}
removeToken()
}
}
})
// 解构
const { setToken, setUserInfo ,clearnUserInfo} = userStore.actions
// 异步登录方法
const fetchLogin = (loginForm) => {
return async (dispatch) => {
const res = await logininApi(loginForm)
dispatch(setToken(res.data.token))
}
}
// 获取用户信息异步方法
const fetchUserInfo = () => {
return async (dispatch) => {
const res = await fetchUserInfoApi()
dispatch(setUserInfo(res.data))
}
}
const userReducer = userStore.reducer
export { setToken, fetchLogin, fetchUserInfo ,clearnUserInfo}
export default userReducer
注: 为了使 token 持久化,需要将 token 存储在 localStorage 中,utils 中写了详细代码
- 在 store/index.js 中引入
import { configureStore } from "@reduxjs/toolkit";
import userReducer from "./moudles/user";
const store = configureStore({
reducer:{
user: userReducer
}
})
export default store
- 在 index.js 中注册
import { Provider } from 'react-redux';
import store from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<RouterProvider router={router} />
</Provider>
);
3.请求拦截器注入 token
- utils/rquest.js
// 添加请求拦截器
request.interceptors.request.use(config => {
// if not login add token
const token = getToken()
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
4.路由鉴权实现
- components/AuthRoute/index.jsx
import { getToken } from '@/utils'
import { Navigate } from 'react-router-dom'
const AuthRoute = ({ children }) => {
const isToken = getToken()
if (isToken) {
return <>{children}</>
} else {
return <Navigate to="/login" replace />
}
}
export default AuthRoute
- src/router/index.jsx
import { createBrowserRouter } from 'react-router-dom'
import Login from '@/pages/Login'
import Layout from '@/pages/Layout'
import AuthRoute from '@/components/Auth'
const router = createBrowserRouter([
{
path: '/',
element: <AuthRoute><Layout /></AuthRoute>,
},
{
path: '/login',
element: <Login />,
},
])
export default router
5.处理 token 失效
request.interceptors.response.use((response) => {
return response.data
}, (error) => {
// 对响应错误做点什么
console.dir(error)
if (error.response.status === 401) {
clearToken()
router.navigate('/login')
window.location.reload()
}
return Promise.reject(error)
})
六、引入 Menu 点击跳转,并反向高亮
1.登录页面后,左侧菜单栏点击跳转,并在地址栏输入路由,菜单栏会自动高亮
2.代码演示
- 点击菜单跳转路由
import { Outlet, useNavigate } from 'react-router-dom'
const items = [
{
label: '首页',
key: '/',
icon: <HomeOutlined />,
},
{
label: '文章管理',
key: '/article',
icon: <DiffOutlined />,
},
{
label: '创建文章',
key: '/publish',
icon: <EditOutlined />,
},
]
const GeekLayout = () => {
const navigate = useNavigate()
const menuClick = (route) => {
navigate(route.key)
}
return (
<Menu
mode="inline"
theme="dark"
selectedKeys={selectedKey}
items={items}
style={{ height: '100%', borderRight: 0 }}
onClick={menuClick}
/>
)
}
export default GeekLayout
- 菜单反向高亮
import { useLocation } from 'react-router-dom'
const GeekLayout = () => {
// 省略部分代码
const location = useLocation()
const selectedKey = location.pathname
return (
<Sider width={200} className="site-layout-background">
<Menu
mode="inline"
theme="dark"
selectedKeys={selectedKey}
items={items}
style={{ height: '100%', borderRight: 0 }}
onClick={menuClickHandler}></Menu>
</Sider>
)
}
8353

被折叠的 条评论
为什么被折叠?



