react搭建后台管理系统
react搭建后台管理系统
一、项目前期的准备工作
-
创建git项目并初始化
-
安装yarn:npm install yarn -g
-
安装node.js
-
yarn init对项目进行初始化,并在命令行根据提示进行必要的设置
"name": "cms-react",
"version": "1.0.0",
"main": "index.js",
"repository": "仓库地址",
"author": "git用户名",
"license": "MIT",
"private": true // 是否是私有仓库
-
webpack安装与配置
- yarn安装:yarn add webpack@3.10.0 --dev
- 在项目根目录创建出口文件webpack.config.js
const path = require('path'); const webpack = require('webpack'); // 引入webpack const HtmlWebpackPlugin = require('html-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { entry: './path/to/my/entry/file.jsx', // 项目的入口文件路径 output: { path: path.resolve(__dirname, 'dist'), // 打包后的项目文件路由,一般不用变 publicPath: '/dist/', // 公共路径 filename: 'js/app.js' // 打包后的入口文件名,根据项目实际情况修改 }, // babel-loader配置 module: { rules: [ // react(jsx)文件的处理配置 { test: /\.m?jsx$/, exclude: /(node_modules)/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env', '@babel/preset-react'], } } }, // css文件的处理配置 { test: /\.css$/i, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: "css-loader" }) }, // sass文件的处理配置 { test: /\.scss$/i, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: ["css-loader", 'sass-loader'] }) }, // url-loader(图片)的处理配置 { test: /\.(png|jpg|gif)$/i, use: [ { loader: 'url-loader', options: { limit: 8192, // 如果超过这个值才会单独生成一个文件 name: 'resource/[name].[ext]' // 文件名及扩展 }, }, ] }, // 字体图标的配置 { test: /\.(eot|svg|ttf|woff|woff2|otf)$/i, use: [ { loader: 'url-loader', options: { limit: 8192, name: 'resource/[name].[ext]' // 文件名及扩展 }, }, ] }, ] }, plugins: [ new HtmlWebpackPlugin({ // 处理html文件 template: './src/index.html' // 项目中自定义模板的路径设置 }), // 处理css文件 new ExtractTextPlugin("css/[name].css"), // 提出公共模块 new webpack.optimize.CommonsChunkPlugin({ name: "common", filename: 'js/base.js' }) ], // devServer配置 devServer: { port: 8086 // 自定义端口号,和主要的软件服务端口号不能冲突 }, }
-
使用webpack打包:项目中webpack没有全局安装,那么打包的命令为node_modules/.bin/webpack
-
安装插件HtmlWebpackPlugin,在Webpack.config.js配置文件中添加安装的插件名。安装命令:npm uninstall html-webpack-plugin npm install html-webpack-plugin@3.2.0 -D,这里注意版本问题,如果版本有问题,在打包的时候会报错:TypeError: Cannot read property 'make' of undefined,安装合适的版本即可
-
安装插件babel-loader,安装命令为: yarn add -D babel-loader @babel/core @babel/preset-env,也可以使用yarn add babel-core@版本号安装多个版本的core
-
安装配置react,命令:
- npm install --save-dev @babel/preset-react
- yarn add react@16.2.0 react-dom@16.2.0
- 安装并配置css插件,命令:yarn add style-loader css-loader --dev
-
安装extract-text-webpack-plugin插件,命令:yarn add extract-text-webpack-plugin@3.0.2 --dev。在打包的时候可能会报错: (node:16920) DeprecationWarning: Tapable.plugin is deprecated. Use new API on
.hooks
instead,或者报错:TypeError: Cannot read property 'thisCompilation' of undefined- 原因:webpack版本在4.0以上,版本不适配
- 解决办法:
- 先卸载执行 npm uninstall --save-dev extract-text-webpack-plugin
- 再安装 npm install --save-dev extract-text-webpack-plugin
-
安装sass-loader,命令:yarn add sass-loader@6.0.6 --dev
-
sass-loader生效的前提是需要安装node-sass, 命令为: yarn add node-sass --dev,这个包和其他的包不同,其他的包都是调用文件,这个包需要调用操作系统的东西。
-
安装url-loader和file-loader用于对图片进行处理,命令为:yarn add file-loader@1.1.6 url-loader@0.6.2 --dev
-
安装字体图标库,命令为:yarn add font-awesome
-
提出处理公共模块插件,需要定义在插件列表中,并且需要提前引入webpack
const webpack = require('webpack'); plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: "common", filename: 'js/base.js' }) ]
-
正常情况下的打包文件命令是:
- node_modules/.bin/webpack(仅在项目中安装webpack时)
- webpack(全局安装webpack时)
-
为了方便打包和查看文件,需要引入webpack-dev-server,做到文件改动服务实时更新的效果
- 命令: yarn add webpack-dev-server@2.9.7 --dev
- 配置:
- 在output中配置:publicPath: '/dist/'
- 在plugins后添加配置信息
- 安装成功后的打包命令为:node_modules/.bin/webpack-dev-server,启动服务
-
启动服务后,如果在项目的调试中报错:Refused to apply style from 'http://localhost:8080/dist/css/style.css' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled. ,解决办法是找到.html文件去掉rel="stylesheet"刷新即可
-
项目的最终打包:
- 需要在package.json配置文件中配置
"scripts":{ "dev": "node_modules/.bin/webpack-dev-server", "dist": "node_modules/.bin/webpack -p" },
- dev:是开发环境的打包, 运行命令为: yarn run dev
- dist: 是线上环境的打包,运行命令为:yarn run dist
二、React框架
(一)、基本的特点
- 视图层框架
- 一个构建用户界面的框架
- 声明式框架
- 数据驱动DOM,再用事件反馈给数据,即数据显示到DOM上和事件反馈给数据是两个独立的换件,在vue和angular上这两个步骤是合并在一起的
- 采用组件化方式
- 组件结合,而不是继承
- state&&props
- jsx表达式
- 一种js扩展的表达式
- 带有逻辑的标记语法,有别于html模版
- 对样式、逻辑表达式和事件的支持
- 虚拟DOM机制,只有在必要的时候才会操作dom,避免低效的操作
- 对DOM进行模拟
- 比较操作前后的数据差异
- 如果有数据差异,统一操作DOM
(二)、基础语法
-
ReactDom.render()用于渲染Dom元素,参数有两个
- 需要渲染的元素标签
- 通过document找到对应的Dom节点
-
jsx文件中标签用className代替普通html中的class
-
在render第一个参数中可以进行包括变量使用、条件判断以及数组循环等多种操作
import React from 'react';
import ReactDom from 'react-dom';
let style = {
color: 'purple',
fontSize: '30px'
}
let name = 'liquanhui'
let names = ['lili', 'yangliu', 'xuhuan']
let flag = false
// 数据的逻辑处理
let jsx = (
<div style={style}>
{/* 变量的使用 */}
<p> I am {name}</p>
{/* 通过条件判断控制变量的使用 */}
{
flag ? <p>I am {name}</p> : <p>I am not {name}</p>
}
{/* 数组循环 */}
{
names.map((name, index) => <p key={index}>Hello, I am {name}</p>)
}
</div>
);
ReactDom.render(
jsx,
document.getElementById('app')
);
(三)、React组件
-
组件的定义方式
-
方式一:
- 先定义组件函数:function functionName(params)
- 把组件的名字以闭合标签的形式添加到ReactDom.render的第一个参数中
-
方式二(推荐使用):
- es6语法下,定义组件需要用到类,病继承React.Component,class className extends React.Component{render(){return ....}}
- 把组件的名字以闭合标签的形式添加到ReactDom.render的第一个参数中
-
需要注意的是:当自定义多个组件的时候,组件不能直接全部添加到ReactDom.render的第一个参数中,需要先添加一个div标签,把所有的组件闭合标签添加到div标签中,div标签作为整体放在第一个参数中
-
import React from 'react';
import ReactDom from 'react-dom';
function Component() {
return <h1>col</h1>
}
// Es6语法
class Es6Component extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'Rosen',
age: 20
}
this.handleClick = this.handleClick.bind(this);
}
// handleClick事件处理
handleClick() {
this.setState({
age: this.state.age + 1
})
}
render() {
setTimeout(() => {
this.setState({
name: 'Rosen Test'
})
}, 2000)
return (
<div>
<h1>I am {this.state.name}</h1>
<p>I am {this.state.age} years old</p>
<button onClick={this.handleClick}>加一岁</button>
</div>
)
}
}
ReactDom.render(
<div>
<Component />
<Es6Component />
</div>,
document.getElementById('app')
);
- React组件的生命周期
- Mounting:挂载阶段
- Updating:运行时阶段
- UnMounting:卸载阶段
- Error Handling:错误处理,这里只处理render渲染时出现的错误,代码逻辑中的错误处理不了
import React from 'react';
import ReactDom from 'react-dom';
class Component extends React.Component {
// 构造方法
constructor(props) {
super(props);
this.state = {
data: 'old state'
}
console.log('初始化数据', 'constructor');
}
// 即将挂载
componentWillMount() {
console.log('componentWillMount');
}
// 挂载完成,组件渲染完成
componentDidMount() {
console.log('componentDidMount');
}
// 将要接收父组件传来的props
componentWillReceiveProps() {
console.log('componentWillReceiveProps');
}
// 子组件是否应该更新,必须有boolen类型的返回值,默认为true
shouldComponentUpdate() {
console.log('shouldComponentUpdate');
return true;
}
// 组件将要更新
componentWillUpdate() {
console.log('componentWillUpdate');
}
// 组件更新完成
componentDidUpdate() {
console.log('conponentDidUpdate');
}
// 即将销毁组件
componentWillUnmount() {
console.log('componentWillUnmount');
}
// 处理点击事件
handleClick() {
console.log('更新数据');
this.setState({
data: 'new state',
hasChild: true
});
}
// 渲染
render() {
console.log('render');
return (
<div>
<div>Props: {this.props.data}</div>
<button onClick={() => { this.handleClick() }}>更新组件状态</button>
</div>
)
}
}
class App extends React.Component{
// 构造方法
constructor(props) {
super();
this.state = {
data: 'old props',
hasChild: true
}
console.log('初始化父组件数据', 'constructor');
}
// 改变props
onPropsChange() {
console.log('更新Props');
this.setState({
data: 'new props'
});
}
// 销毁子组件
destroyClick() {
console.log('销毁子组件');
this.setState({
hasChild: false
})
}
render() {
return (
<div>
{
this.state.hasChild ? <Component data={this.state.data} /> : null
}
<button onClick={() => { this.onPropsChange() }}>改变props</button>
<br/>
<button onClick={() => { this.destroyClick()}}>销毁子组件</button>
</div>
)
}
}
ReactDom.render(
<div>
<App />
</div>,
document.getElementById('app')
)
/**
* 生命周期顺序
*/
初始化父组件数据 constructor
初始化数据 constructor
componentWillMount
render
componentDidMount
更新数据
shouldComponentUpdate
componentWillUpdate
render
conponentDidUpdate
更新Props
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
render
conponentDidUpdate
销毁子组件
componentWillUnmount
(四)、Router原理及React-router
-
常见的Router
- 页面Router:整个页面重新渲染加载
- Hash Router:只有页面的hash值改变,整个页面并不会重新加载
- H5 Router:保证在完成路由的同时,不进行页面的跳转。既能操作hash,也可以操作路径,但由于h5的原因,兼容性要差一些
-
有了react-router之后,就可以开发spa应用了。spa(single page application):单页应用。就是所有的功能都在一个页面上完成,和传统pc端网页的都是a连接跳转方式不同,主要类似于native app(原生app:一般底部都有导航栏,在做切换的时候页面上的很多内容会被复用)的体验。
-
安装命令:yarn add react-router-dom@4.2.2
import React from 'react';
import ReactDom from 'react-dom';
import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom';
class A extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
Component A
<Switch>
<Route exact path={`${this.props.match.path}`} render={(route) => {
return <div>当前组件是不带参数的A</div>
}} />
{/**
* 为了保证路由可以正常的访问,在路由的设置中必须把子路由放在通配参数路由之前,否则子路由无法访问
*/}
<Route path={`${this.props.match.path}/sub`} render={(route) => {
return <div>子路由信息</div>
}} />
<Route path={`${this.props.match.path}/:id`} render={(route) => {
return <div>当前组件是带参数的A,参数是:{route.match.params.id}</div>
}} />
</Switch>
</div>
)
}
}
class B extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
Component B
</div>
)
}
}
class Wrapper extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Link to="/a">组件A</Link>
<br />
{/*带参数的路由*/}
<Link to="/a/123">带参数的组件A</Link>
<br />
{/*子路由*/}
<Link to="/a/sub">子路由</Link>
<br/>
<Link to="/b">组件B</Link>
{this.props.children}
</div>
)
}
}
ReactDom.render(
<Router>
<Wrapper>
<Route path="/a" component={A} />
<Route path="/b" component={B} />
</Wrapper>
</Router>,
document.getElementById('app')
)
(五)、React数据管理
-
数据通信的方式
- 方式一:状态提升方式。找到不同组件之间的共同祖先组件,当一个组件对共同祖先组件修改之后,其他的子组件对应的数据也会发生相应的改变
graph LR 组件A --通过组件B对祖先组件数据进行修改--> 组件B 组件B-->共同祖先组件 共同祖先组件--组件A对祖先组件修改后,其他组件数据发生改变-->组件C 组件C-->组件D- 方式二:发布订阅方式。不同组件之间存在一个共同委会的订阅中心,每一个组件把自己的数据发布到订阅中心,其他的组件按照对应的数据结构接收数据,这种方式不再像状态提升方式一样需要寻找共同的祖先组件。
graph TB 组件A--传入-->订阅中心[(订阅中心)] 组件B--传入-->订阅中心[(订阅中心)] 组件C--传入-->订阅中心[(订阅中心)] 组件D--传入-->订阅中心[(订阅中心)] 订阅中心[(订阅中心)]--接收-->组件A 订阅中心[(订阅中心)]--接收-->组件B 订阅中心[(订阅中心)]--接收-->组件C 订阅中心[(订阅中心)]--接收-->组件D- 方式三:Redux单向数据流的方式,这种方式更类似于状态提升和订阅消息两种方式的结合,不同于状态提升的是,不同组件之间通信的中间组件不是共同祖先组件,而是所有组件的根组件(Store),组件发出action,action定义要做什么,Reducer接收,根据原有的action和state生成新的state,对根组件进行数据操作,根组件发生变化后,所有的组件数据也发生相应的改变。每一个组件都可以是改变方,同样的每一个组件都可以是改变后的数据的接收方。
graph TB 组件A--action-->Reducer Reducer--state-->store[(根组件Store)] store[(根组件Store)]--数据改变-->组件C store[(根组件Store)]--数据改变-->组件D store[(根组件Store)]--数据改变-->组件E store[(根组件Store)]--数据改变-->组件F store[(根组件Store)]--数据改变-->组件G -
应用场景:
- 状态提升方式:组件层级扁平,兄弟组件通信情况很少的业务场景
- 发布订阅:业务规模叫小,层级较深的业务场景
- Redux:业务复杂,组件层级较深,兄弟组件通信密切等业务场景都可以很好的解决,但由于复杂度比较高,所以在状态提升和发布订阅方式不能很好解决的必要情状下,再使用Redux单向数据方式
三、管理系统开发
(一)、通用部分开发
- 引用Element UI用于构建页面
- 命令:npm i element-react --save npm install element-theme-default --save
- 引入后,如果直接启动,会报两种错误:
- Module not found: Error: Can't resolve 'babel-runtime/helpers/typeof'
- Module not found: Error: Can't resolve 'babel-runtime/helpers/possibleConstructorReturn'
- 解决办法:
- yarn add babel-runtime
- npm i @babel/runtime --save-dev
- npm i -D react-hot-loader@next