快速构建Web应用,从零学习React后台项目模版

编程

想要快速构建实际应用,离不开一个好的应用模版,React作为大厂出品工具,有着稳定性和可维护性的保障,同时可以使用相关的全套全家桶(React + React-router + Axios + Mobx + Antd)进行连贯敏捷开发,本文将从如何在云开发平台创建项目应用模版,基于应用模版创建《后台管理》项目,以及上传并且通过云平台将项目上线部署应用,为项目开发提供更加便捷的操作环境。

一 、通过云开发平台快速创建初始化应用

1.创建相关应用模版请参考链接:https://developer.aliyun.com/article/878171?spm=a2c6h.12873581.0.dArticle878171.c61253e8nVBtAv

2.完成创建后就可以在github中查看到新增的react仓库

二 、本地编写《后台管理》项目

1.将应用模版克隆到本地

• 首先假定你已经安装了Git、node,没有安装请移步node官网进行安装。克隆项目:

git clone + 项目地址

• 进入项目文件

cd create-react-app

• 切换到feature/1.0.0 分支上

git checkout feature/1.0.0

• 使用一下命令全局安装 React :

npm install -g create-react-app

• 安装依赖包

npm install

• 启动服务

npm start

这里打开浏览器3000端口,并出现默认页面。

2.架构与效果预览

• 《后台管理》项目架构


• 效果预览

3.初始化项目

• 初始化package.json

npm init

• 安装webpack

npm add -D webpack webpack-cli webpack-merge

项目中使用的Webpack版本是^5.10.0,Webpack4.0 打包构建做了很多默认的优化配置,不少配置项无需配置或更改。

比如:针对开发模式的加快打包速度,合并chunk; 针对生产模式的代码压缩,减少打包体积等。

// 一部分默认配置  

optimization: {

removeAvailableModules: true, // 删除已解决的chunk (默认 true)

removeEmptyChunks: true, // 删除空的chunks (默认 true)

mergeDuplicateChunks: true // 合并重复的chunk (默认 true)

}

// 针对生产环境默认配置

optimization: {

sideEffects:true, //配合tree shaking

splitChunks: {...}, //拆包

namedModules: false, // namedChunks:false 不启用chunk命名,默认自增id

minimize: true, // 代码压缩

}

根据开发环境/生产环境 区分webpack配置非常有必要,可以加快开发环境的打包速度,有时候遇到开发环境打包过慢,可以排查下是否配置有误(比如开发环境开启了代码压缩等)。

项目中配合webpack-merge根据开发环境/生产环境进行拆分配置:


Webpack4.0发布已经很长时间了,相信基本上项目都已迁移至4.0,在这里就不多赘述了。

• 配置Html模版

安装:

npm add -D html-webpack-plugin

配置:

const srcDir = path.join(__dirname, "../src");

plugins: [

new HtmlWebpackPlugin({

template: `${srcDir}/index.html`

})

]

• 配置本地服务及热更新

安装:

npm add -D webpack-dev-server clean-webpack-plugin

开发环境利用webpack-dev-server搭建本地 web server,并启用模块热更新(HMR)。

为方便开发调试,转发代理请求(本例中配合axios封装 转发接口到easy-mock在线平台)

配置:

mode: "development", // 开发模式

devServer: { // 本地服务配置

port: 9000,

hot: true,

open: false,

historyApiFallback: true,

compress: true,

proxy: { // 代理

"/testapi": {

target:

"https://www.easy-mock.com/mock/5dff0acd5b188e66c6e07329/react-template",

changeOrigin: true,

secure: false,

pathRewrite: { "^/testapi": "" }

}

}

},

plugins: [

new webpack.NamedModulesPlugin(),

new webpack.HotModuleReplacementPlugin()

],

• 配置Babel

安装:

npm add -D babel-loader @babel/core @babel/plugin-transform-runtime 

@babel/preset-env @babel/preset-react babel-plugin-import

@babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators

Webpack中Babel配置,是比较重要的一环。关系着ES6语法、React jsx、Mobx等语法经过打包后能否正常运行。

其中:

@babel/preset-react转换React jsx语法;

@babel/plugin-proposal-class-properties 转换 Class语法;

@babel/plugin-proposal-decorators 转换 Mobx 等更高级的语法;

babel-plugin-import 配合实现React组件的按需加载;

这里需要注意Babel7.0 相较于Babel6.0的区别。

配置:

module: {

rules: [

{

test: /.(js|jsx)$/,

include: [srcDir],

use: ["babel-loader?cacheDirectory=true"]

},

]

}

• .babelrc 文件配置

{

"presets": [

"@babel/preset-env",

"@babel/preset-react"

],

"plugins": [

"@babel/transform-runtime",

[

"@babel/plugin-proposal-decorators",

{

"legacy": true

}

],

["@babel/plugin-proposal-class-properties", { "loose": true }],

[

"import",

{

"libraryName": "antd",

"libraryDirectory": "es",

"style": "css" // `style: true` 会加载 less 文件

}

]

]

}

• 处理Less样式和图片等资源

安装:

npm add -D less less-loader style-loader css-loader url-loader 

mini-css-extract-plugin postcss-loader autoprefixer

其中:

less-loader、style-loader、css-loader处理加载less、css文件;

postcss-loader、autoprefixer处理css样式浏览器前缀兼容;

url-loader处理图片、字体文件等资源;

mini-css-extract-plugin 分离css成单独的文件;

配置:

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

...

module: {

rules: [

{

test: /.less$/,

use: [

devMode ? "style-loader" : MiniCssExtractPlugin.loader,

"css-loader",

"postcss-loader",

"less-loader"

]

},

{

test: /.css$/,

use: [

devMode ? "style-loader" : MiniCssExtractPlugin.loader,

"css-loader",

"postcss-loader"

]

},

{

test: /.(png|jpe?g|gif|svg)(?.*)?$/,

use: ["url-loader"],

include: [srcDir]

},

{

test: /.(mp4|webm|ogg|mp3|wav|flac|aac)(?.*)?$/,

use: ["url-loader"],

include: [srcDir]

},

{

test: /.(woff2?|eot|ttf|otf)(?.*)?$/,

use: ["url-loader"],

include: [srcDir]

}

]

},

plugins: [

new MiniCssExtractPlugin({

filename: "[name].[contenthash:8].css",

chunkFilename: "chunk/[id].[contenthash:8].css"

}),

],

配置postcss .postcssrc.js 文件

// .postcssrc.js

module.exports = {

plugins: {

autoprefixer: {}

}

};

// package.json中配置兼容浏览器

"browserslist": [

"> 1%",

"last 2 versions",

"not ie <= 10"

]

• 利用happypack多线程打包

安装:

npm add -D happypack

配置:

const os = require("os");

const HappyPack = require("happypack");

const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });

module: {

rules: [

{

test: /.(js|jsx)$/,

include: [srcDir],

exclude: /(node_modules|bower_components)/,

use: ["happypack/loader?id=happybabel"]

},

]

},

plugins: [

//开启 happypack 的线程池

new HappyPack({

id: "happybabel",

loaders: ["babel-loader?cacheDirectory=true"],

threadPool: happyThreadPool,

cache: true,

verbose: true

}),

]

• 生产环境 拆分模块

根据实际项目情况拆分模块,配合异步加载,防止单个文件过大。

optimization: {

runtimeChunk: {

name: "manifest"

},

splitChunks: {

chunks: "all", //默认只作用于异步模块,为`all`时对所有模块生效,`initial`对同步模块有效

cacheGroups: {

dll: {

test: /[\/]node_modules[\/](react|react-dom|react-dom-router|babel-polyfill|mobx|mobx-react|mobx-react-dom|antd|@ant-design)/,

minChunks: 1,

priority: 2,

name: "dll"

},

codeMirror: {

test: /[\/]node_modules[\/](react-codemirror|codemirror)/,

minChunks: 1,

priority: 2,

name: "codemirror"

},

vendors: {

test: /[\/]node_modules[\/]/,

minChunks: 1,

priority: 1,

name: "vendors"

}

}

}

}

• 其他配置

引入 ESLint 与 Prettier 配合,规范化团队项目代码开发,统一代码风格。

npm add -D prettier babel-eslint eslint eslint-loader eslint-config-airbnb 

eslint-config-prettier eslint-plugin-babel eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react

具体配置详见 /build目录 下 https://github.com/now1then/react-web-pro/tree/master/build

• npm scripts

package.json 文件

{

...

"scripts": {

"start": "webpack-dev-server --color --inline --progress --config build/webpack.dev.js", //

"build": "NODE_ENV=production webpack --progress --config ./build/webpack.prod.js",

"build:report": "NODE_ENV=production webpack --progress --config ./build/webpack.prod.js",

"build:watch": "NODE_ENV=production webpack --progress --config ./build/webpack.prod.js"

},

...

}

命令行运行:

// 命令行执行

// 运行开发环境;

npm start

// 生产环境打包压缩;

npm build

// 图形化分析打包文件大小;

npm build:report

// 方便排查生产环境打包后文件的错误信息(文件source map);

npm build:watch

其中 build:report 、build:watch 能够实现功能,是在build/webpack.prod.js 中有如下代码:

// 方便排查生产环境打包后文件的错误信息(文件source map)

if (process.env.npm_lifecycle_event == "build:watch") {

config = merge(config, {

devtool: "cheap-source-map"

});

}

// 图形化分析打包文件大小

if (process.env.npm_lifecycle_event === "build:report") {

const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")

.BundleAnalyzerPlugin;

config.plugins.push(new BundleAnalyzerPlugin());

}

• 项目代码架构

npm add react react-dom react-router-dom mobx mobx-react mobx-react-router 

axios antd moment

4.函数化Hooks

当前React版本已更新到16.12,Hooks 完全应该成为 React 使用的主流。本项目中将完全拥抱Hook,一般不再用 class 来实现组件。

以下为部分实现代码(可暂忽略mobx的使用):

import React, { useState, useEffect, useContext } from "react";

import { observer } from "mobx-react";

import { Button } from "antd";

import Store from "./store";

import "./style.less";

const HomePage = () => {

// useContext 订阅mobx数据

const pageStore = useContext(Store);

// useState state状态

const [num, setNum] = useState(0);

// useEffect副作用

useEffect(() => {

pageStore.qryTableDate();

}, []);

return (

<div className="page-home page-content">

<h2>{pageStore.pageTitle}</h2>

<div>

<span>num值:{num}</span>

<Button type="primary" size="small" style={{ marginLeft: 10 }}

onClick={() => setNum(num + 1)}

>+1</Button>

</div>

</div>

);

};

export default observer(HomePage);

5.Router路由配置

项目是单页应用,路由配置一般分为约定式动态路由和集中配置式路由。

在 React 的世界里,直接采用成熟的react-router工具管理页面路由。我们现在说到react-router,基本上都是在说 react-router 的第4版之后的版本,当前的最新版本已经更新到5.1.x了。

当前react-router支持动态路由,完全用React组件来实现路由,在渲染过程中动态设置路由规则,匹配命中规则加载对应页面组件。

本项目采用集中配置式路由(方便路由鉴权、从服务端接口获取菜单路由配置等),同时兼顾方便地设置侧边菜单栏。 当然为简单起见,项目中读取本地静态菜单配置,也暂未引入路由鉴权。

6.静态路由配置 src/routes/config.js :

import React, { lazy } from "react";

import BasicLayout from "@/layouts/BasicLayout";

import BlankLayout from "@/layouts/BlankLayout";

const config = [

{

path: "/",

component: BlankLayout, // 空白页布局

childRoutes: [ // 子菜单路由

{

path: "/login", // 路由路径

name: "登录页", // 菜单名称 (不设置,则不展示在菜单栏中)

icon: "setting", // 菜单图标

component: lazy(() => import("@/pages/Login")) // 懒加载 路由组件

},

// login等没有菜单导航栏等基本布局的页面, 要放在基本布局BasicLayout之前。

{

path: "/",

component: BasicLayout, // 基本布局框架

childRoutes: [

{

path: "/welcome",

name: "欢迎页",

icon: "smile",

component: lazy(() => import("@/pages/Welcome"))

},

{... /* 其他 */},

{ path: "/", exact: true, redirect: "/welcome" },

{ path: "*", exact: true, redirect: "/exception/404" }

]

}

]

}

];

export default config;

上面是静态路由的一部分配置,

注意:中会用包裹,会匹配命中的第一个。"/login"等没有菜单导航栏等基本布局的页面, 要放在基本布局BasicLayout之前。

利用和React.lazy()实现页面组件懒加载。

7.路由组建渲染 src/routes/AppRouter.js :

import React, { lazy, Suspense } from "react";

import LoadingPage from "@/components/LoadingPage";

import {

HashRouter as Router,

Route,

Switch,

Redirect

} from "react-router-dom";

import config from "./config";

const renderRoutes = routes => {

if (!Array.isArray(routes)) {

return null;

}

return (

<Switch>

{routes.map((route, index) => {

if (route.redirect) {

return (

<Redirect

key={route.path || index}

exact={route.exact}

strict={route.strict}

from={route.path}

to={route.redirect}

/>

);

}

return (

<Route

key={route.path || index}

path={route.path}

exact={route.exact}

strict={route.strict}

render={() => {

const renderChildRoutes = renderRoutes(route.childRoutes);

if (route.component) {

return (

<Suspense fallback={<LoadingPage />}>

<route.component route={route}>

{renderChildRoutes}

</route.component>

</Suspense>

);

}

return renderChildRoutes;

}}

/>

);

})}

</Switch>

);

};

const AppRouter = () => {

return <Router>{renderRoutes(config)}</Router>;

};

export default AppRouter;

8.路由 hooks 语法

react-router-dom 也已经支持 hooks语法,获取路由信息或路由跳转,可以使用新的hooks 函数:

• useHistory:获取历史路由,回退、跳转等操作;

• useLocation:查看当前路由信息;

• useParams:读取路由附带的params参数信息;

• useRouteMatch:匹配当前路由;

只要包裹在中的子组件都可以通过这几个钩子函数获取路由信息。

代码演示:

import { useHistory } from "react-router-dom";

function HomeButton() {

const history = useHistory();

function onClick() {

history.push("/home");

}

return (

<button type="button" onClick={onClick}>

跳转Home页

</button>

);

}

9.结合mobx管理数据状态

项目中是否使用状态管理工具或使用何种管理工具,依据实际项目情况而定。

本项目使用自己比较熟悉的Mobx,Mobx是一个功能强大,上手非常容易的状态管理工具。

为了使用简洁及管理方便,在组织上,分为全局公共数据状态和页面数据状态。

公用数据状态存放在/src/stores目录下;页面几数据存放于对应页面目录下。

在实现上,利用mobx + useContext Hook特性 实现函数式组件的状态管理。

具体在于利用React的createdContext构建包含Mobx 的context上下文;函数式组件中使用useContext Hook 订阅Mobx数据变化。

• 页面级store.js 代码:

import { createContext } from "react";

import { observable, action, computed } from "mobx";

import request from "@/services/newRequest";

class HomeStore {

@observable tableData = [];

@observable pageTitle = "Home主页";

@observable loading = false;

@action.bound setData(data = {}) {

Object.entries(data).forEach(item => {

this[item[0]] = item[1];

});

}

// 列表数据

@action.bound

async qryTableDate(page = 1, size = 10) {

this.loading = true;

const res = await request({

url: "/list",

method: "post",

data: { page, size }

});

if (res.success) {

const resData = res.data || {};

console.log(resData);

}

this.loading = false;

}

}

export default createContext(new HomeStore());

• 页面组件代码 :

import React, { useContext } from "react";

import { observer } from "mobx-react";

import Store from "./store";

import "./style.less";

const HomePage = () => {

const pageStore = useContext(Store);

return (

<div className="page-home page-content">

home页面

<h2>{pageStore.pageTitle}</h2>

</div>

);

};

export default observer(HomePage);

以上为部分演示代码,具体业务实现可以查看项目代码。

10.Axios Http 请求封装

Axios请求封装,具体代码见 /src/services/newRequest.js

11.UI组件及页面布局

UI组件使用优秀的Ant Design 组件库,注意使用 babel-plugin-import 配置实现组件的按需加载。

本项目的内部页面布局采用 Antd 上经典的布局方式:


页面布局需要合理拆分模块,左侧菜单导航栏根据静态菜单渲染。实际完整代码详见项目,以下为BasicLayout组件:

import React from "react";

import { Layout } from "antd";

import SiderMenu from "../SiderMenu";

import MainHeader from "../MainHeader";

import MainFooter from "../MainFooter";

import "./style.less";

const BasicLayout = ({ route, children }) => {

return (

<Layout className="main-layout">

{/* 左侧菜单导航 */}

<SiderMenu routes={route.childRoutes} />

<Layout className="main-layout-right">

{/* 顶部展示布局 */}

<MainHeader></MainHeader>

<Layout.Content className="main-layout-content">

{/* 实际页面布局 */}

{children}

{/* <MainFooter></MainFooter> */}

</Layout.Content>

</Layout>

</Layout>

);

};

export default BasicLayout;

对于登录页等页面无需套在上面的基本布局之类,需要单独处理(菜单配置在BasicLayout配置之前)。

三 、云端一键部署上线应用

1.上传代码

git add . 

git commit -m "添加你的注释"

git push

2.在日常环境部署

一键进行应用部署。在应用详情页面点击日常环境的「部署」按钮进行一键部署,部署状态变成绿色已部署以后可以点击访问部署网站查看效果。

3.配置自定义域名在线上环境上线

• 配置线上环境自定义域名。在功能开发验证完成后要在线上环境进行部署,在线上环境的「部署配置」-「自定义域名」中填写自己的域名。例如我们添加一个二级域名 company.workbench.fun 来绑定我们部署的前端应用。然后复制自定义域名下方的API网关地址对添加的二级域名进行CNAME配置。


• 配置CNAME地址。复制好 API网关域名地址后,来到你自己的域名管理平台(此示例中的域名管理是阿里云的域名管理控制台,请去自己的域名控制台操作)。添加记录的「记录类型」选择「CNAME」,在「主机记录」中输入你要创建的二级域名,这里我们输入「company」,在「记录值」中粘贴我们之前复制的 API网关域名地址,「TTL」保留默认值或者设置一个你认为合适的值即可。


• 在线上环境部署上线。回到云开发平台的应用详情页面,按照部署的操作,点击线上环境的「部署按钮」,部署完成以后就在你自定义的域名进行了上线。CNAME 生效之后,我们输入 company.workbench.fun(示例网址) 可以打开部署的页面。至此,如何部署一个应用到线上环境,如何绑定自己的域名来访问一个线上的应用就完成了,赶紧部署自己的应用到线上环境,用自己的域名玩起来吧 ;)


一键创建React应用模版链接 :https://workbench.aliyun.com/application/front/create?fromConfig=12&fromRepo=sol_github_12

以上是 快速构建Web应用,从零学习React后台项目模版 的全部内容, 来源链接: utcz.com/z/520311.html

回到顶部