想要疾速构建理论利用,离不开一个好的利用模版,React 作为大厂出品工具,有着稳定性和可维护性的保障,同时能够应用相干的全套全家桶(React + React-router + Axios + Mobx + Antd)进行连贯麻利开发,本文将从如何在云开发平台创立我的项目利用模版,基于利用模版创立《后盾治理》我的项目,以及上传并且通过云平台将我的项目上线部署利用,为我的项目开发提供更加便捷的操作环境。
一、通过云开发平台疾速创立初始化利用
1. 创立相干利用模版请参考链接:https://developer.aliyun.com/…
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/r…
• 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/…
参考文献:https://juejin.cn/post/684490…