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
posted @ 2020-08-22 10:21  大道至诚  阅读(2443)  评论(0编辑  收藏  举报