一、项目启动
- 了解需求背景
- 了解业务流程
二、项目搭建初始化
本案例使用脚手架 create-react-app 初始化了项目。此脚手架有利有弊,项目目录结构简洁,不需要太关心webpack令人头疼的配置;弊端在于,脚手架确实有些庞大,构建时间在 4mins 左右。也可以完全自己搭建一个项目。设置淘宝镜像仓库
$ yarn config set registry registry.npm.taobao.org/ -g$ yarn config set sass_binary_site cdn.npm.taobao.org/dist/node-sass -g
工程目录 init
$ create-react-app qpj-web-pc --typescript$ tree -I "node_modules".|-- README.md|-- package.json|-- public| |-- favicon.ico| |-- index.html| |-- logo192.png| |-- logo512.png| |-- manifest.json| `-- robots.txt|-- src| |-- App.css| |-- App.test.tsx| |-- App.tsx| |-- index.css| |-- index.tsx| |-- logo.svg| |-- react-app-env.d.ts| |-- reportWebVitals.ts| `-- setupTests.ts`-- tsconfig.json
yarn build$ yarn build & tree -I "node_modules".|-- README.md|-- build/ # 改造点(由于 `Jenkins` 构建打包脚本有可能已经写死了 `dist` 包名)|-- package.json|-- public| |-- favicon.ico| |-- index.html| |-- logo192.png| |-- logo512.png| |-- manifest.json| `-- robots.txt|-- src| |-- App.css| |-- App.test.tsx| |-- App.tsx| |-- index.css| |-- index.tsx| |-- logo.svg| |-- react-app-env.d.ts| |-- reportWebVitals.ts| `-- setupTests.ts`-- tsconfig.json
连接 git 远程仓库
$ git remote add origin
添加 .gitignore
$ echo -e " yarn.lock \n package-lock.json \n /dist \n .idea" >> .gitignore
添加
eslint代码及提交评论校验$ yarn add husky lint-staged @commitlint/cli @commitlint/config-conventional -D$ npx husky install$ npx husky add .husky/pre-commit "npx lint-staged"$ npx husky add .husky/prepare-commit-msg "npx commitlint -e"
项目根目录新建 commitlint.config.js
// commitlint.config.jsmodule.exports = {extends: ['@commitlint/config-conventional'],rules: {'type-enum': [2,'always',['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore', 'revert'],],'subject-full-stop': [0, 'never'],'subject-case': [0, 'never'],},}
vscode 扩展中搜索 ESLint 并安装,项目根目录新建 .eslintrc.js
- Commit message 格式说明
- type 值枚举如下:
- feat: 添加新特性
- fix: 修复 bug
- docs: 仅仅修改了文档
- style: 仅仅修改了空格、格式缩进、都好等等,不改变代码逻辑
- refactor: 代码重构,没有加新功能或者修复 bug
- perf: 增加代码进行性能测试
- test: 增加测试用例
- chore: 改变构建流程、或者增加依赖库、工具等
- revert: 当前 commit 用于撤销以前的 commit
- subject 是 commit 目的的简短描述,不超过 50 个字符,且结尾不加句号(.)
package.json 新加入如下配置:
{...,"lint-staged": {"src/**/*.{jsx,txs,ts,js,json,css,md}": ["eslint --quiet"]},}
可执行
npx eslint [filePath] \--fix进行格式修复,无法修复的需手动解决三、项目配置一(功能配置)
安装项目常用依赖库
$ yarn add antd axios dayjs qs -S # UI 库 及工具库$ yarn add react-router-dom redux react-redux redux-logger redux-thunk -S # 路由及状态管理
webpack 配置拓展很有必要
根目录新建 config-overrides.js
- 安装
$ yarn add react-app-rewired customize-cra -D修改 package.json 中启动项
// package.json"scripts": {"start": "react-app-rewired start","build": "react-app-rewired build",}
使用 ```jsx // config-overrides.js const { override, // 主函数 fixBabelImports, // 配置按需加载 addWebpackExternals, // 不做打包处理配置 addWebpackAlias, // 配置别名 addLessLoader // lessLoader 配置,可更改主题色等 } = require(‘customize-cra’)
module.exports = override(/ … /, config => config)
<a name="wt6t0"></a>### 配置按需加载```jsx// config-overrides.js...module.exports = override(fixBabelImports('import', {libraryName: 'antd',libraryDirectory: 'es', // library 目录style: true, // 自动打包相关的样式}),)
更改主题色
// config-overrides.js...module.exports = override(addLessLoader({lessOptions: {javascriptEnabled: true,modifyVars: {'@primary-color': '#1890ff',},}}),)
别名配置(typescript 项目这里有坑)
// config-overrides.jsconst path = require('path')...module.exports = override(addWebpackAlias({'@': path.resolve(__dirname, 'src'),}),)
去除注释、多进程打包压缩
// config-overrides.jsconst UglifyJsPlugin = require('uglifyjs-webpack-plugin')const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')...module.exports = override(/* ... */, config => {config.plugins = [...config.plugins, {new UglifyJsPlugin({uglifyOptions: {warnings: false,compress: {drop_debugger: true,drop_console: true,},},}),new HardSourceWebpackPlugin()}]return config})
解决埋下的两个坑
修改打包出的文件夹名为 dist
// 修改打包路径除了output,这里也要修改const paths = require('react-scripts/config/paths')paths.appBuild = path.join(path.dirname(paths.appBuild), 'dist')module.exports = override(/* ... */, config => {config.output.path = path.resolve(__dirname, 'dist')return config})
解决 typescript 别名配置
- 查阅相关资料,需要在 tsconfig.json 中添加一项配置
{..."extends": "./paths.json"}
新建文件 paths.json
{"compilerOptions": {"baseUrl": "src","paths": {"@/*": ["*"]}}}
配置装饰器写法
{"compilerOptions": {"experimentalDecorators": true,...}}
配置开发代理
- 在 src 目录新建 setupProxy.js ```jsx // src/setupProxy.js const proxy = require(‘http-proxy-middleware’).createProxyMiddleware
module.exports = function(app) { // app 为 Express 实例,此处可以写 Mock 数据 app.use( proxy(‘/api’, { “target”: “https://qpj-test.fapiaoer.cn“, “changeOrigin”: true, “secure”: false, // “pathRewrite”: { // “^/api”: “” // } }) ) }
<a name="uRhGJ"></a>### 加入 polyfill 和 antd 组件国际化处理```jsx// src/index.tsximport React from 'react'import ReactDOM from 'react-dom'// 注入 storeimport { Provider } from 'react-redux'import store from '@/store/store'import { ConfigProvider, Empty } from 'antd'import App from './App'import zhCN from 'antd/es/locale/zh_CN'import 'moment/locale/zh-cn'// polyfillimport 'core-js/stable'import 'regenerator-runtime/runtime'ReactDOM.render(<Provider store={store}><ConfigProvider locale={zhCN} renderEmpty={Empty}><App /></ConfigProvider></Provider>,document.getElementById('root'))
CSS Modules
create-react-app 自带支持以 xxx.module.(c|le|sa)ss 的样式表文件,使用上 typescript 项目中要注意:
const styles = require('./index.module.less')retrun (<div className={`${styles.container}`}><Tablecolumns={columns}className={`${styles['border-setting']}`}dataSource={props.store.check.items}rowKey={record => record.id}pagination={false}/><div className="type-check-box"></div></div>)
// index.module.less.container {padding: 24px;background-color: #fff;height: 100%;overflow: auto;.border-setting {tr {td:nth-child(3) {border-left: 1px solid #F0F0F0;border-right: 1px solid #F0F0F0;}}td {text-align: left !important;}}:global { // 这个标识之后,其子代元素可以不需要使用 `styles['type-check-box']` 的方式,直接写 `className`.type-check-box {.ant-checkbox-wrapper + .ant-checkbox-wrapper{margin-left: 0;}}}}
【新】配置 React jsx 指令式属性 r-if、r-for、r-model、r-show,提升开发效率:
安装依赖
$ yarn add babel-react-rif babel-react-rfor babel-react-rmodel babel-react-rshow -D
配置 .babelrc :
// .babelrc{...,"plugins": ["babel-react-rif","babel-react-rfor","babel-react-rmodel","babel-react-rshow",]}
使用示例:
r-if<div><h1 r-if={height < 170}>good</h1><h1 r-else-if={height > 180}>best</h1><h1 r-else>other</h1></div>
r-for{/* eslint-disable-next-line no-undef */}<div r-for={(item, index) in [1, 2, 3, 4]} key={index}>内容 {item + '-' + index}</div>
r-model<input onChange={this.callback} type="text" r-model={inputVale} />
r-show<div r-show={true}>内容</div> # 注意:这是 `r-if` 的效果,不会渲染节点
四、项目配置二(优化配置)
实现组件懒加载 react-loadable
```typescript import Loadable from ‘react-loadable’
const Loading = (props: any) => { if (props.error) { console.error(props.error) return
const loadable = (path: any) => {
return Loadable({
loader: () => import(@/pages${path}),
loading: Loading,
delay: 200,
timeout: 10000,
})
}
const Home = loadable(‘/homePage/Home’)
<a name="KZWMh"></a>### 处理 axios 拦截响应```typescriptconst service = axios.create({baseURL: '/',timeout: 15000,})service.interceptors.request.use(function (config) {return config})service.interceptors.response.use(function (config) {return config})
处理 React router 的嵌套配置
React 中不支持类似 Vue Router 路由配置方式,React 中一切皆组件,路由也是组件,需要用到路由要临时加上路由组件,写起来就很繁琐,但可以自己实现路由配置表方式。
// router/router.config.tsconst routes = [{path: '/home',component: loadable('components/Index'),exact: true,},{path: '/new',component: loadable('components/New'),redirect: '/new/list',// exact: true,routes: [{path: '/new/list',component: loadable('components/NewList'),exact: true,},{path: '/new/content',component: loadable('components/NewContent'),exact: true,},],},]export default routes// router/router.tsimport React from 'react'import { Switch, BrowserRouter as Router, Route } from 'react-router-dom'import routes from './index'function mapRoutes(routes: any[], store: object): any {return routes.map((item: any, index: number) => {return (<Route exact={item.exact || false} path={item.path} key={index} render={props => {const NewComp = item.componentObject.assign(props, {redirect: item.redirect || null,permission: item.permission || [],...store})if (item.routes) {return <NewComp {...props}>{ mapRoutes(item.routes, store) }</NewComp>} else {return <NewComp {...props} />}}} />)})}const Routes = (props: any) => {return (<Router><Switch>{ mapRoutes(routes, props.store) }<Route component={() => (<div>404 Page not Found!</div>)} /></Switch></Router>)}export default Routes
子路由承载页面需要加上如下代码:
import { Redirect, Route, Switch } from 'react-router-dom'<Switch>{props.children}<Route component={() => (<div>404 Page not Found!</div>)} />{props.redirect && <Redirect to={props.redirect} />}</Switch>
处理 React store
// store/store.tsimport { createStore, applyMiddleware } from 'redux'import thunk from 'redux-thunk'import logger from 'redux-logger'import reducers from './reducer'const store = process.env.NODE_ENV === 'development'? createStore(reducers, applyMiddleware(thunk, logger)): createStore(reducers, applyMiddleware(thunk))export default store
为了方便使用,避免每个组件都需要 connect ,这边实现了 redux store 的全局注入,但是如果项目庞大的话就会损耗些性能。
// App.tsximport { dispatchActions } from '@/store/reducer'export default connect((state: any) => ({ store: state }), dispatchActions)(App)
