想要快速构建实际应用,离不开一个好的应用模版,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官网进行安装。克隆项目:
gitclone+项目地址
- 进入项目文件
cdcreate-react-app
- 切换到feature/1.0.0 分支上
gitcheckoutfeature/1.0.0
- 使用一下命令全局安装 React :
npminstall-gcreate-react-app
- 安装依赖包
npminstall
- 启动服务
npmstart
这里打开浏览器3000端口,并出现默认页面。
2.架构与效果预览
- 《后台管理》项目架构
- 效果预览
3.初始化项目
- 初始化package.json
npminit
- 安装webpack
npmadd-Dwebpackwebpack-cliwebpack-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 shakingsplitChunks: {...}, //拆包namedModules: false, // namedChunks:false 不启用chunk命名,默认自增idminimize: true, // 代码压缩 }
根据开发环境/生产环境 区分webpack配置非常有必要,可以加快开发环境的打包速度,有时候遇到开发环境打包过慢,可以排查下是否配置有误(比如开发环境开启了代码压缩等)。
项目中配合webpack-merge根据开发环境/生产环境进行拆分配置:
Webpack4.0发布已经很长时间了,相信基本上项目都已迁移至4.0,在这里就不多赘述了。
- 配置Html模版
安装:
npmadd-Dhtml-webpack-plugin
配置:
constsrcDir=path.join(__dirname, "../src"); plugins: [ newHtmlWebpackPlugin({ template: `${srcDir}/index.html` }) ]
- 配置本地服务及热更新
安装:
npmadd-Dwebpack-dev-serverclean-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: [ newwebpack.NamedModulesPlugin(), newwebpack.HotModuleReplacementPlugin() ],
- 配置Babel
安装:
npmadd-Dbabel-loader@babel/core@babel/plugin-transform-runtime@babel/preset-env@babel/preset-reactbabel-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样式和图片等资源
安装:
npmadd-Dlessless-loaderstyle-loadercss-loaderurl-loadermini-css-extract-pluginpostcss-loaderautoprefixer
其中:
less-loader、style-loader、css-loader处理加载less、css文件;
postcss-loader、autoprefixer处理css样式浏览器前缀兼容;
url-loader处理图片、字体文件等资源;
mini-css-extract-plugin 分离css成单独的文件;
配置:
constMiniCssExtractPlugin=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: [ newMiniCssExtractPlugin({ filename: "[name].[contenthash:8].css", chunkFilename: "chunk/[id].[contenthash:8].css" }), ],
配置postcss .postcssrc.js 文件
// .postcssrc.jsmodule.exports= { plugins: { autoprefixer: {} } }; // package.json中配置兼容浏览器"browserslist": [ "> 1%", "last 2 versions", "not ie <= 10"]
- 利用happypack多线程打包
安装:
npmadd-Dhappypack
配置:
constos=require("os"); constHappyPack=require("happypack"); consthappyThreadPool=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 的线程池newHappyPack({ 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 配合,规范化团队项目代码开发,统一代码风格。
npmadd-Dprettierbabel-eslinteslinteslint-loadereslint-config-airbnbeslint-config-prettiereslint-plugin-babeleslint-plugin-importeslint-plugin-jsx-a11yeslint-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" }, ... }
命令行运行:
// 命令行执行
// 运行开发环境;
npmstart
// 生产环境打包压缩;
npmbuild
// 图形化分析打包文件大小;
npmbuild:report
// 方便排查生产环境打包后文件的错误信息(文件source map);
npmbuild: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") { constBundleAnalyzerPlugin=require("webpack-bundle-analyzer") .BundleAnalyzerPlugin; config.plugins.push(newBundleAnalyzerPlugin()); }
- 项目代码架构
npmaddreactreact-domreact-router-dommobxmobx-reactmobx-react-routeraxiosantdmoment
4.函数化Hooks
当前React版本已更新到16.12,Hooks 完全应该成为 React 使用的主流。本项目中将完全拥抱Hook,一般不再用 class 来实现组件。
以下为部分实现代码(可暂忽略mobx的使用):
importReact, { useState, useEffect, useContext } from'react'; import { observer } from'mobx-react'; import { Button } from'antd'; importStorefrom'./store'; import'./style.less'; constHomePage= () => { // useContext 订阅mobx数据constpageStore=useContext(Store); // useState state状态const [num, setNum] =useState(0); // useEffect副作用useEffect(() => { pageStore.qryTableDate(); }, []); return ( <divclassName="page-home page-content"><h2>{pageStore.pageTitle}</h2><div><span>num值:{num}</span><Buttontype="primary"size="small"style={{ marginLeft: 10 }} onClick={() =>setNum(num+1)} >+1</Button></div></div> ); }; exportdefaultobserver(HomePage);
5.Router路由配置
项目是单页应用,路由配置一般分为约定式动态路由和集中配置式路由。
在 React 的世界里,直接采用成熟的react-router工具管理页面路由。我们现在说到react-router,基本上都是在说 react-router 的第4版之后的版本,当前的最新版本已经更新到5.1.x了。
当前react-router支持动态路由,完全用React组件来实现路由,在渲染过程中动态设置路由规则,匹配命中规则加载对应页面组件。
本项目采用集中配置式路由(方便路由鉴权、从服务端接口获取菜单路由配置等),同时兼顾方便地设置侧边菜单栏。 当然为简单起见,项目中读取本地静态菜单配置,也暂未引入路由鉴权。
6.静态路由配置 src/routes/config.js :
importReact, { lazy } from"react"; importBasicLayoutfrom"@/layouts/BasicLayout"; importBlankLayoutfrom"@/layouts/BlankLayout"; constconfig= [ { 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" } ] } ] } ]; exportdefaultconfig;
上面是静态路由的一部分配置,
注意:<Router>中会用<Switch>包裹,会匹配命中的第一个。"/login"等没有菜单导航栏等基本布局的页面, 要放在基本布局BasicLayout之前。
利用<Suspense>和React.lazy()实现页面组件懒加载。
7.路由组建渲染 src/routes/AppRouter.js :
importReact, { lazy, Suspense } from"react"; importLoadingPagefrom"@/components/LoadingPage"; import { HashRouterasRouter, Route, Switch, Redirect} from"react-router-dom"; importconfigfrom"./config"; constrenderRoutes=routes=> { if (!Array.isArray(routes)) { returnnull; } return ( <Switch> {routes.map((route, index) => { if (route.redirect) { return ( <Redirectkey={route.path||index} exact={route.exact} strict={route.strict} from={route.path} to={route.redirect} /> ); } return ( <Routekey={route.path||index} path={route.path} exact={route.exact} strict={route.strict} render={() => { constrenderChildRoutes=renderRoutes(route.childRoutes); if (route.component) { return ( <Suspensefallback={<LoadingPage/>}><route.componentroute={route}> {renderChildRoutes} </route.component></Suspense> ); } returnrenderChildRoutes; }} /> ); })} </Switch> ); }; constAppRouter= () => { return<Router>{renderRoutes(config)}</Router>; }; exportdefaultAppRouter;
8.路由 hooks 语法
react-router-dom 也已经支持 hooks语法,获取路由信息或路由跳转,可以使用新的hooks 函数:
- [useHistory](https://reacttraining.com/react-router/core/api/Hooks/usehistory):获取历史路由,回退、跳转等操作;
- useLocation:查看当前路由信息;
- [useParams](https://reacttraining.com/react-router/core/api/Hooks/useparams):读取路由附带的params参数信息;
- [useRouteMatch](https://reacttraining.com/react-router/core/api/Hooks/useroutematch):匹配当前路由;
只要包裹在中的子组件都可以通过这几个钩子函数获取路由信息。
代码演示:
import { useHistory } from"react-router-dom"; functionHomeButton() { consthistory=useHistory(); functiononClick() { history.push("/home"); } return ( <buttontype="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"; importrequestfrom"@/services/newRequest"; classHomeStore { @observabletableData= []; @observablepageTitle="Home主页"; @observableloading=false; @action.boundsetData(data= {}) { Object.entries(data).forEach(item=> { this[item[0]] =item[1]; }); } // 列表数据@action.boundasyncqryTableDate(page=1, size=10) { this.loading=true; constres=awaitrequest({ url: "/list", method: "post", data: { page, size } }); if (res.success) { constresData=res.data|| {}; console.log(resData); } this.loading=false; } } exportdefaultcreateContext(newHomeStore());
- 页面组件代码 :
importReact, { useContext } from"react"; import { observer } from"mobx-react"; importStorefrom"./store"; import"./style.less"; constHomePage= () => { constpageStore=useContext(Store); return ( <divclassName="page-home page-content">home页面<h2>{pageStore.pageTitle}</h2></div> ); }; exportdefaultobserver(HomePage);
以上为部分演示代码,具体业务实现可以查看项目代码。
10.Axios Http 请求封装
Axios请求封装,具体代码见 /src/services/newRequest.js
11.UI组件及页面布局
UI组件使用优秀的Ant Design 组件库,注意使用 babel-plugin-import 配置实现组件的按需加载。
本项目的内部页面布局采用 Antd 上经典的布局方式:
页面布局需要合理拆分模块,左侧菜单导航栏根据静态菜单渲染。实际完整代码详见项目,以下为BasicLayout组件:
importReactfrom"react"; import { Layout } from"antd"; importSiderMenufrom"../SiderMenu"; importMainHeaderfrom"../MainHeader"; importMainFooterfrom"../MainFooter"; import"./style.less"; constBasicLayout= ({ route, children }) => { return ( <LayoutclassName="main-layout"> {/* 左侧菜单导航 */} <SiderMenuroutes={route.childRoutes} /><LayoutclassName="main-layout-right"> {/* 顶部展示布局 */} <MainHeader></MainHeader><Layout.ContentclassName="main-layout-content"> {/* 实际页面布局 */} {children} {/* <MainFooter></MainFooter> */} </Layout.Content></Layout></Layout> ); }; exportdefaultBasicLayout;
对于登录页等页面无需套在上面的基本布局之类,需要单独处理(菜单配置在BasicLayout配置之前)。
三 、云端一键部署上线应用
1.上传代码
gitadd . gitcommit-m'添加你的注释'gitpush
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
参考文献:https://juejin.cn/post/6844904035099623437
【特别活动】阿里云云开发平台发布两年啦,感谢你对云开发长期的支持!快来分享你在云开发当中的开发感受,心得体会,以及成长经验吧!(包含但不仅限于文章、个人作品、毕业设计,企业应用等)。
所有在云开发平台做过相关应用开发的同学,分享并提交我与云开发的故事均可以获得优酷VIP月卡一张,我们会选择50个用户的故事和留言发放云开发优秀用户奖,前三名用户还将额外获得AirPods无线耳机一副,同时也会在相应的社区、媒体上对用户故事进行宣传。