第一步:使用cra脚手架 创建项目
文档地址:Create React App 中文文档
npx create-react-app '你的项目名称'
第二步:整理项目结构和删除多余代码
目标效果图:
在src目录下分别新建apis,assets,components,pages,router,store,utlis文件夹
原始项目目录结构只保留App.js,index.css,index.js这个三个文件
App.js:
index.css:
index.js:
拓展:安装scss
npm install sass -D
测试和使用:
App.js内容:
把index.css改为index.scss
index.js内容:
效果:
第三步:安装ui组件库-Ant Design
官网链接:在 create-react-app 中使用 - Ant Design
npm install antd --save
测试使用:
src/App.js:
import { Button } from 'antd';
function App() {
return (
<div>
<Button type="primary">Button</Button>
</div>
);
}
export default App;
效果图:
第四步:配置路由
中文文档:在开始之前 | React Router6 中文文档
第一步:安装
npm install react-router-dom
第二步:新建两个页面文件
在src/pages下新建两个文件Layout和Login
Layout/index.js:
const Layout= ()=>{
return(
<div>
我是Layout
</div>
)
}
export default Layout
Login/index.js:
const Login= ()=>{
return(
<div>
我是Login
</div>
)
}
export default Login
第三步:新建路由实例
src/router下新建index.js
import Layout from "../pages/Layout";
import Login from "../pages/Login";
import { createBrowserRouter } from "react-router-dom";
// 配置路由
const router = createBrowserRouter([
{
path: '/',
element: <Layout></Layout>
},
{
path: '/login',
element: <Login></Login>
}
])
export default router
第四步:使用挂载实例
在src/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';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<RouterProvider router={router}></RouterProvider>
</React.StrictMode>
);
效果图:
第五步:封装request
第一步:修改src/pages/Login/index.js文件
import { Card, Button, Form, Input, message } from 'antd';
import './index.scss'
import { useDispatch } from 'react-redux';
import { fetchLogin } from '../../store/modules/user';
import { useNavigate } from 'react-router-dom';
const Login = () => {
const navigate= useNavigate()
// 实例的账号密码
// 13800000002
// 246810
const dispatch = useDispatch()
const onFinish =async (values) => {
await dispatch(fetchLogin(values))
// 跳转
navigate('/')
// 提示
message.success('登录成功')
console.log('Success:', values);
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
return (
<div>
<Card className='card'>
<Form
name="basic"
labelCol={{
span: 8,
}}
wrapperCol={{
span: 16,
}}
style={{
maxWidth: 600,
}}
initialValues={{
remember: true,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<Form.Item
label="Username"
name="mobile"
rules={[
{
required: true,
message: 'Please input your username!',
},
]}
>
<Input />
</Form.Item>
<Form.Item
label="Password"
name="code"
rules={[
{
required: true,
message: 'Please input your password!',
},
]}
>
<Input.Password />
</Form.Item>
<Form.Item
wrapperCol={{
offset: 8,
span: 16,
}}
>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
</Card>
</div>
)
}
export default Login
在src/pages/Login下添加index.scss
.card{
display: flex;
justify-content: center;
position: fixed;
top: 100px;
left:35%;
width: 500px;
}
第二步:封装request
中文文档:起步 | Axios中文文档 | Axios中文网
安装axios
npm install axios
在src/utlis下新建request.js和index.js
request.js:
// axios封装
// 1 根域名配置
// 2 超时时间
// 3 请求拦截器
// 4 响应拦截器
import axios from "axios";
import { getToken } from "./token";
const request = axios.create({
baseURL:'http://geek.itheima.net/v1_0',
timeout:5000
})
// 添加请求拦截器
request.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
const token= getToken()
if(token){
config.headers.Authorization='Bearer '+token
}
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
request.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response.data;
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
});
export {request}
index.js:
// 统一中转工具函数导出
import { request } from "./request";
import { setToken,getToken,removeToken } from "./token";
export {
request,
setToken,
getToken,
removeToken
}
第六步:利用redux封装token
第一步安装react-redux/@reduxjs/toolkit
中文文档:入门 Redux | Redux 中文官网
npm install @reduxjs/toolkit react-redux
在src/store新建modules/user.js和index.js
src/store/index.js:
// 组合子模块
import { configureStore } from "@reduxjs/toolkit";
import userStore from "./modules/user";
const store = configureStore({
reducer:{
userStore
}
})
export default store
src/store/modules/user.js:
// 放置和用户相关的
import { createSlice } from "@reduxjs/toolkit";
import { request,setToken as _setToken,getToken } from "../../utils";
const userSlice= createSlice({
name:'user',
initialState:{
// token持久化
token:getToken()||'',
},
reducers:{
setToken:(state,action)=>{
state.token=action.payload
// token持久化
_setToken(action.payload)
}
}
})
// 解构
const {setToken} = userSlice.actions
// 获取reducer函数
const userStore= userSlice.reducer
// 封装一部方法 完成登录 获取token
const fetchLogin=(loginForm)=>{
return async (dispatch)=>{
// 1 发送异步请求
const res = await request.post('/authorizations',loginForm)
// 2 提交同步修改方法
dispatch(setToken(res.data.token))
}
}
export {fetchLogin,setToken}
export default userStore
最后在src/index.js添加store
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.scss';
import router from './router';
import { RouterProvider } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from './store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<RouterProvider router={router}></RouterProvider>
</Provider>
</React.StrictMode>
);
然后在login进行调用
封装存,取,删token的方法
在src/utlis新建token.js
const TOKENKEY='token'
const setToken =(token)=>{
localStorage.setItem(TOKENKEY,token)
}
const getToken=()=>{
return localStorage.getItem(TOKENKEY)
}
const removeToken=()=>{
localStorage.removeItem(TOKENKEY)
}
export {setToken,getToken,removeToken}
在src/pages/Layout下进行测试
// 测试toekn
import { useEffect } from "react"
import { request } from "../../utils"
const Layout= ()=>{
useEffect(()=>{
request.get('/user/profile')
},[])
return(
<div>
我是Layout
</div>
)
}
export default Layout
第七步:根据token进行路由权限判断
在src/components下新建Auth.js
// 封装高阶组件
// 有token正常访问 无token跳转login
import { getToken } from "../utils"
import { Navigate } from "react-router-dom"
const Auth = ({children}) => {
const token = getToken()
if(token){
return <>{children}</>
}else{
return <Navigate to={'/login'} replace></Navigate>
}
}
export default Auth
然后在router/index.js修改
import Layout from "../pages/Layout";
import Login from "../pages/Login";
import { createBrowserRouter } from "react-router-dom";
import Auth from "../components/Auth";
import Home from "../pages/Home";
import Article from "../pages/Article";
import Publish from "../pages/Publish";
// 配置路由
const router = createBrowserRouter([
{
path: '/',
element: <Auth><Layout></Layout></Auth>,
children:[
{
index:true,
element:<Home></Home>
},
{
path:'article',
element:<Article></Article>
}, {
path:'publish',
element:<Publish></Publish>
}
]
},
{
path: '/login',
element: <Login></Login>
}
])
export default router
新建src/pages下的Home,Article,Publish文件
Home.js:
const Home = ()=>{
return(
<div>我是Home</div>
)
}
export default Home
下面两个文件类推!!!
第八步:封装Laout样式组件
覆盖src/pages/Layout/index.js
import React, { useEffect, useState } from 'react';
import {
MenuFoldOutlined,
MenuUnfoldOutlined,
UploadOutlined,
UserOutlined,
VideoCameraOutlined,
} from '@ant-design/icons';
import { Button, Layout, Menu, theme, Popconfirm } from 'antd';
import { Outlet, useNavigate } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { clearUserInfo, fetchUserInfo } from '../../store/modules/user';
const { Header, Sider, Content } = Layout;
const App = () => {
const navigste = useNavigate()
const dispatch = useDispatch()
const [collapsed, setCollapsed] = useState(false);
const {
token: { colorBgContainer, borderRadiusLG },
} = theme.useToken();
// 侧边跳转
const MenuClick = (key) => {
navigste(key)
}
useEffect(() => {
dispatch(fetchUserInfo())
}, [dispatch])
const userInfo = useSelector(state => state.userStore.userInfo)
// 退出按钮
const confirm=()=>{
dispatch(clearUserInfo())
navigste('/login')
}
return (
<Layout>
<Sider trigger={null} collapsible collapsed={collapsed}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<img src='https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg' style={{ width: '50%', height: '50px' }} alt=''></img>
{!collapsed &&
<div style={{ color: '#fff', width: '50%' }}>哈哈哈</div>
}
</div>
<Menu
onClick={({ key }) => MenuClick(key)}
theme="dark"
mode="inline"
defaultSelectedKeys={['/']}
items={[
{
key: '/',
icon: <UserOutlined />,
label: '首页',
},
{
key: '/article',
icon: <VideoCameraOutlined />,
label: '文章',
},
{
key: '/publish',
icon: <UploadOutlined />,
label: '发布',
},
]}
/>
</Sider>
<Layout>
<Header
style={{
padding: '0 24px 0 0',
background: colorBgContainer,
display: 'flex',
justifyContent: 'space-between'
}}
>
<Button
type="text"
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
onClick={() => setCollapsed(!collapsed)}
style={{
fontSize: '16px',
width: 64,
height: 64,
}}
/>
<div style={{ display: 'flex', alignItems: 'center', fontSize: '16px' }}>
<span>{userInfo.name}</span>
<UserOutlined style={{ marginLeft: '10px' }} />
<Popconfirm
title="退出"
description="确认退出吗?"
onConfirm={confirm}
okText="Yes"
cancelText="No"
>
<span style={{ cursor: "pointer" }}>退出</span>
</Popconfirm>
</div>
</Header>
<Content
style={{
margin: '24px 16px',
padding: 24,
minHeight: 280,
background: colorBgContainer,
borderRadius: borderRadiusLG,
}}
>
{/* 二级路由出口 */}
<Outlet></Outlet>
</Content>
</Layout>
</Layout>
);
};
export default App;
封装退出方法
src/store/modules/user.js
// 放置和用户相关的
import { createSlice } from "@reduxjs/toolkit";
import { request,setToken as _setToken,getToken, removeToken } from "../../utils";
const userSlice= createSlice({
name:'user',
initialState:{
// token持久化
token:getToken()||'',
userInfo:{}
},
reducers:{
setToken:(state,action)=>{
state.token=action.payload
// token持久化
_setToken(action.payload)
},
setUserInfo:(state,action)=>{
state.userInfo=action.payload
},
clearUserInfo:(state)=>{
state.token=''
state.userInfo={}
removeToken()
}
}
})
// 解构
const {setToken,setUserInfo,clearUserInfo} = userSlice.actions
// 获取reducer函数
const userStore= userSlice.reducer
// 封装异步方法 完成登录 获取token
const fetchLogin=(loginForm)=>{
return async (dispatch)=>{
// 1 发送异步请求
const res = await request.post('/authorizations',loginForm)
// 2 提交同步修改方法
dispatch(setToken(res.data.token))
}
}
// 获取个人信息
const fetchUserInfo=()=>{
return async (dispatch)=>{
// 1 发送异步请求
const res = await request.get('/user/profile')
// 2 提交同步修改方法
dispatch(setUserInfo(res.data))
}
}
export {fetchLogin,setToken,fetchUserInfo,clearUserInfo}
export default userStore
第九步:处理token失效
src/utlis/request.js
// axios封装
// 1 根域名配置
// 2 超时时间
// 3 请求拦截器
// 4 响应拦截器
import axios from "axios";
import { getToken, removeToken } from "./token";
import router from "../router";
const request = axios.create({
baseURL:'http://geek.itheima.net/v1_0',
timeout:5000
})
// 添加请求拦截器
request.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
const token= getToken()
if(token){
config.headers.Authorization='Bearer '+token
}
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
request.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response.data;
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
// 处理token失效
if(error.response.status===401){
removeToken()
router.navigate('/login')
window.location.reload()
}
return Promise.reject(error);
});
export {request}
第十步:封装Api请求
src/apis/user.js
import { request } from "../utils";
const loginApi=(data)=>{
return request({
url:'/authorizations',
method:'post',
data
})
}
const userInfoApi=()=>{
return request({
url:'/user/profile',
method:'get',
})
}
export {loginApi,userInfoApi}
修改src/store/modules/user.js
// 放置和用户相关的
import { createSlice } from "@reduxjs/toolkit";
import {setToken as _setToken,getToken, removeToken } from "../../utils";
import { loginApi, userInfoApi } from "../../apis/user";
const userSlice= createSlice({
name:'user',
initialState:{
// token持久化
token:getToken()||'',
userInfo:{}
},
reducers:{
setToken:(state,action)=>{
state.token=action.payload
// token持久化
_setToken(action.payload)
},
setUserInfo:(state,action)=>{
state.userInfo=action.payload
},
clearUserInfo:(state)=>{
state.token=''
state.userInfo={}
removeToken()
}
}
})
// 解构
const {setToken,setUserInfo,clearUserInfo} = userSlice.actions
// 获取reducer函数
const userStore= userSlice.reducer
// 封装异步方法 完成登录 获取token
const fetchLogin=(loginForm)=>{
return async (dispatch)=>{
// 1 发送异步请求
const res = await loginApi(loginForm)
// 2 提交同步修改方法
dispatch(setToken(res.data.token))
}
}
// 获取个人信息
const fetchUserInfo=()=>{
return async (dispatch)=>{
// 1 发送异步请求
const res = await userInfoApi()
// 2 提交同步修改方法
dispatch(setUserInfo(res.data))
}
}
export {fetchLogin,setToken,fetchUserInfo,clearUserInfo}
export default userStore
到这里恭喜你完成了一个简单版本的react后台模板
示例代码地址:react-demo-pc: 练习使用的一个react后台模板,主要熟悉联系ui组件库,redux,router,axios