前言
本文主要使用React全家桶一步步搭建单页管理应用,阅读本文需要React、React-Redux、React-Router 基础知识,使用react 脚手架 create-react-app 创建项目
提示:以下是本篇文章正文内容,下面案例可供参考,如有问题,欢迎指正。
一、实现效果
环境
windows10 + node
二、搭建步骤
1.创建项目
在cmd窗口中执行:
# 安装 create-react-app ( react脚手架,已安装,请忽略)
npm i create-react-app -g
# 创建项目
npx create-react-app day7-react-cli
项目目录如下:
目录说明:
│ .gitignore # git 忽略文件
│ package.json # 项目说明文件,定义项目依赖、描述等信息
│ README.md # 项目说明文档
│ yarn.lock # yarn 版本锁定文件
├─ public # 静态资源文件夹,该文件夹下的文件不会被打包处理,直接复制
│ favicon.ico # 浏览器标签栏图标
│ index.html # 项目唯一html 入口模板文件 react root元素
│ logo192.png
│ logo512.png
│ manifest.json # pwa 文件
│ robots.txt # 自定义爬虫文件
└─ src
App.css # App 组件的样式
App.js # App组件
App.test.js # App组件测试示例文件
index.css # 项目入口index.js 文件的样式
index.js # 项目入口文件
logo.svg
reportWebVitals.js
setupTests.js
2.运行项目
执行命令:
# 进入项目目录
cd day7-react-cli
# 安装依赖(我这边使用的是yarn,如果用npm: npm i 即可)
yarn
# 运行项目(npm run start)
yarn start
控制台看见如下就代表运行成功:
会自动打开浏览器窗口,展示如下:
三、项目改造
1.添加redux-router、redux-router、axios、antd依赖
# 状态管理库
yarn add react-redux -S
# 适用于浏览器的路由
yarn add react-router-dom -S
# ajax请求库
yarn add axios -S
# ui 组件库
yarn add antd -S
2.修改后目录,及主要修改文件
│ .gitignore
│ jsconfig.json
│ LICENSE
│ package.json
│ README.md
│ yarn.lock
├─ public
│ favicon.ico
│ index.html
│ logo192.png
│ logo512.png
│ manifest.json
│ robots.txt
└─ src
│ App.css
│ App.js
│ App.test.js
│ index.css
│ index.js
│ reportWebVitals.js
│ setupProxy.js
│ setupTests.js
├─ assets # 静态资源文件夹
│ └─images
│ logo.svg
├─ layout # 页面主体布局,展示左侧菜单及头部
│ │ app-main.css
│ │ app-main.jsx
│ └─ components
│ │ content.jsx # 页面二级路由
│ │ header.jsx # 头部,logo 头像等
│ │ sider.jsx # 左侧菜单
│ └─css
│ content.css
│ header.css
│ sider.css
├─ redux # 状态管理store 和各action
│ │ redux.js
│ ├─ action
│ │ user-action.js
│ └─ reducer
│ user-reducer.js
├─ route
│ main-router.jsx # 页面一级路由
└─ views # 页面各组件
├─ home
│ home.jsx
├─ login # 登录页,我这边讲 UI 和 reducer 分开了,实际可以合并在一起
│ │ login.jsx
│ └─ui
│ login.css
│ login.jsx
├─ page1
│ page1.jsx
└─ page2
page2.jsx
主要文件代码:
index.js:
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import { Provider } from "react-redux";
import store from "redux/redux";
// Provider 为自组件提供store对象
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
App.js:
import "./App.css";
import MainRouter from "route/main-router";
const App = () => <MainRouter />;
export default App;
MainRouter.js:
import { Component } from "react";
// 这里引如 BrowserRouter 适合浏览器的路由实现
import {
BrowserRouter as Router,
Route,
Switch,
Redirect,
} from "react-router-dom";
import "layout/app-main.css";
import Login from "views/login/login";
import AppMain from "layout/app-main";
export default class MainRouter extends Component {
// 此处渲染 第一层路由
render() {
return (
<Router>
<Switch>
<Route path="/login" component={Login}></Route>
<Route path="/app" component={AppMain}></Route>
<Redirect path="/" to="/app"></Redirect>
</Switch>
</Router>
);
}
}
/src/layout/app-main.jsx:
这里使用antd 布局,完成管理页面经典布局,实际MainContent 可以直接把代码拿到这边写,我这边拆开不影响
由于app-main 组件为 react-router 直接渲染的组件,所以他的props中有history对象,可以用作菜单跳转,我们这里可以传一个跳转菜单的方法到 SiderMenu 组件中用作点击左侧菜单跳转页面,这种方法有点麻烦,我这边直接用了高阶组件 withRuter,被他包裹的组件的props可以拥有history,用它完成路由跳转
import React, { Component } from "react";
import { Layout } from "antd";
import SiderMenu from "layout/components/sider";
import MainContent from "layout/components/content";
import HeaderWrapper from "layout/components/header";
const { Header, Sider, Content } = Layout;
/**
* 页面的主要架构,考虑是否需要添加登陆判断
*/
export default class AppMain extends Component {
render() {
return (
<Layout className="app_main">
<Header>
<HeaderWrapper></HeaderWrapper>
</Header>
<Layout className="app_main_content">
<Sider>
<SiderMenu></SiderMenu>
</Sider>
<Content>
<MainContent></MainContent>
</Content>
</Layout>
</Layout>
);
}
}
/src/components/sider.jsx:
import { Component } from "react";
import { Menu } from "antd";
import { AppstoreOutlined, MailOutlined } from "@ant-design/icons";
import { withRouter } from "react-router-dom";
import "./css/sider.css";
const { SubMenu } = Menu;
const menus = {
home: [],
menu1: ["page1", "page2"],
menu2: ["page3", "page4", "page5", "page6", "page8"],
menu3: ["page9", "page10", "page11", "page12"],
};
class SiderMenu extends Component {
state = {
openKeys: [],
activeKeys: [],
};
componentDidMount() {
let { pathname } = this.props.location;
const key = pathname.split("/")[2] || "home";
this.setOpenKeys(key);
this.setState({
activeKeys: [key],
});
}
setOpenKeys = (key) => {
for (let tmp in menus) {
let menu = menus[tmp];
if (menu.includes(key)) {
this.setState({
openKeys: [tmp],
});
break;
} else {
this.setState({
openKeys: [],
});
}
}
};
handleOpenChange = (openKeys) => {
if (openKeys.length === 0) {
this.setState({
openKeys: [],
});
} else {
this.setState({
openKeys: [openKeys[openKeys.length - 1]],
});
}
};
handleMenuClick = ({ key }) => {
this.setOpenKeys(key);
this.setState({
activeKeys: [key],
});
// 被包裹组件,因为他不是Router 直接渲染的组件,如果不包裹它没有history
this.props.history.push(`/app/${key}`);
};
render() {
return (
<Menu
theme="dark"
mode="inline"
openKeys={this.state.openKeys}
onOpenChange={this.handleOpenChange}
onClick={this.handleMenuClick}
selectedKeys={this.state.activeKeys}
>
<Menu.Item key="home">首页</Menu.Item>
<SubMenu key="menu1" icon={<MailOutlined />} title="测试1">
<Menu.Item key="page1">菜单 1</Menu.Item>
<Menu.Item key="page2">菜单 2</Menu.Item>
</SubMenu>
<SubMenu key="menu2" icon={<AppstoreOutlined />} title="测试2">
<Menu.Item key="page5">菜单 5</Menu.Item>
<Menu.Item key="page6">菜单 6</Menu.Item>
<Menu.Item key="page7">菜单 7</Menu.Item>
<Menu.Item key="page8">菜单 8</Menu.Item>
</SubMenu>
<SubMenu key="menu3" icon={<AppstoreOutlined />} title="测试3">
<Menu.Item key="page9">菜单 page9</Menu.Item>
<Menu.Item key="page10">菜单 page10</Menu.Item>
<Menu.Item key="page11">菜单 page11</Menu.Item>
<Menu.Item key="page12">菜单 page12</Menu.Item>
</SubMenu>
</Menu>
);
}
}
// 这边返回包裹后的组件
export default withRouter(SiderMenu);
注意下,在项目根目录里面添加了一个setupProxy.js 用作配置代理服务器地址规则,前后端分离的项目,如果后端不允许跨域,需要我们自己用代理来完成ajax请求
setupProxy.js:
// 如下配置会将请求本地地址3000/student/* 代理到5000
const createProxyMiddleware = require("http-proxy-middleware");
module.exports = function (app) {
app.use(
"/student",
createProxyMiddleware({
target: "http://localhost:5000",
changeOrigin: true,
pathRewrite: {
"^/student": "",
},
})
);
// 如下配置会将请求本地地址3000/person/* 代理到6000
app.use(
"/person",
createProxyMiddleware({
target: "http://localhost:6000",
changeOrigin: true,
pathRewrite: {
"^/person": "",
},
})
);
};
请求的地方:
// 这边可以直接填本地的地址,
axios.get("/student/login").then((res) => {
this.props.saveUserInfo({
...res.data,
});
this.props.history.push("/app/home");
});