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
.hooksinstead,或者報錯: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
