1. 环境搭建
npx create-react-app myapp
版本:
1 | "react": "^16.13.1", |
1.1 安装webpack
npm install --save-dev webpack-cli webpack webpack-merge webpack-dev-server clean-webpack-plugin
具体步骤见 https://jiafei2333.github.io/2019/10/12/Webpack-base/
1.2 安装loader
npm install style-loader css-loader --save-dev
npm install less less-loader --save-dev
npm install file-loader --save-dev
1.3 安装babel
npm install @babel/core @babel/preset-env babel-loader --save-dev
npm i @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators --save-dev
npm install core-js@2 --save
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
npm install --save-dev @babel/plugin-syntax-dynamic-import
具体步骤见 https://jiafei2333.github.io/2019/11/13/Webpack-js/
1.4 安装开发环境
npm install --save redux
npm install --save react-redux
npm install --save redux-saga
npm install --save react-router-dom
npm install redux-thunk redux-logger --save
npm install mini-css-extract-plugin --save-dev
// 抽离css文件
1.5 配置node环境
新建 nodejs/server.jsnodejs server.js
启动服务
可以安装nodemon node的监视器 监视文件变化:npm install nodemon -g 使用: nodemon 文件名(可以增加配置文件)
yarn add express
yarn add cors
1.6 基础目录创建
1.7 UI组件库
npm install antd --save
1.8 错误处理
2. 基础功能
2.1 将redux与react-router 连接
2.1.1 前情
src/redux/actions/home.js
1 | import {fetchLoginAdd} from '../actionServer/home'; |
在redux actions中可以用dispatch(push(‘/xxxx’))切换路由
通过 connected-react-router 和 history 两个库将 react-router 与 redux 进行深度整合实现。
npm install connect-react-router history --save
然后给 store 添加如下配置:
- 创建
history
对象,因为我们的应用是浏览器端,所以使用createBrowserHistory
创建 - 使用connectRouter包裹 root reducer 并且提供我们创建的history对象,获得新的 root reducer
- 使用routerMiddleware(history)实现使用 dispatch history actions,这样就可以使用push(‘/path/to/somewhere’)去改变路由(这里的 push 是来自 connected-react-router 的)
store.js
1 | import {createStore, applyMiddleware} from 'redux'; |
src/reducers/reducers.js
1 | import {combineReducers} from 'redux'; |
根组件中,添加如下配置
- 使用ConnectedRouter包裹路由,并且将 store 中创建的history对象引入,作为 props 传入应用
- ConnectedRouter组件要作为Provider的子组件
index.js
1 | import React from 'react'; |
这样就将 redux 与 react-router 整合完毕了。
2.2 顶部加载动画 NProgress
npm install --save nprogress
2.3 路由登录判断
src/router.js
1 | import React from 'react'; |
src/components/Base/PrivateRoute.js
1 | import React from 'react'; |
2.4 使用redux-saga
关于为什么将redux-thunk换成redux-saga,及redux-saga的知识点查看 #5.1 #5.2
src/redux/sagas/rootSaga.js
1 | // 登录 |
/src/store/store.js
1 | import {createStore, applyMiddleware, compose} from 'redux'; |
src/pages/Home/LoginIndex.js
1 | onSubmit(value){ |
3. 疑问
3.1 push跳转
在redux/actions中dispatch(push("/home"))
做了路由跳转,url地址栏改变了,但是页面没有切换
src/router.js
1 | import React from 'react'; |
现在将line10、line16注释,可以跳转,url和页面都切换。
3.2 引入less文件
直接import 引入不了 ??import Style from './style.less';
Style打印出一个空对象 ??
改成import './style.less';
直接引入className='class的名字'
3.3 全局下的菜单栏数据
我的想法是没有做数据交互的组件都写成函数组件,所以这里写顶部菜单栏组件一开始写的是函数组件,如下:
1 | import React from 'react'; |
但是当首页componentDidMount中获取数据,redux reducer数据改变,页面没有更新,所以只能改为Class Component,如下:
1 | import React, { Component } from 'react'; |
当mainMenu数据变化的时候页面菜单自动刷新显示。
3.4 路由问题
3.4.1 路由保护
PrivateRoute.js 文件 这样写组件加载失败,之前好几周都是好好的,不知道为什么突然不行了??
3.4.2 刷新问题
【问题】
三级路由localhost:3000/editorialCenter/auditing/auditPending
刷新界面又 http://localhost:3000/editorialCenter/auditing/bundle.js 404 (Not Found)
【解决】
webpack.base.js
1 | output:{ |
3.4.3 BrowserRouter 重定向问题
和 HashRouter 不同的是,BrowserRouter路由跳转会根据 /xxx 后面的具体页面去服务器请求,在开发模式下可以配置如下:
1 | devServer:{ |
在生产环境下,我这里的后台是.net环境,远程桌面是用IIS配置的站点环境,只要在服务器端下载安装 https://www.iis.net/downloads/microsoft/url-rewrite,然后在打包好的跟目录下添加项目中的 web.config
文件即可。
3.5 优化问题
3.5.1 babel
babel-import-plugin
在.babelrc中配置
1 | "plugins": [ |
跟没有配置对比,多出了将近300k的css文件,其他文件的大小都是一样的 ???为啥
4. 报错
4.1 ant 引入.less后缀的样式文件
1 | // import 'antd/dist/antd.css'; |
将 build/webpack.base.js
1 | { |
修改为
1 | { |
报错
当前版本 "less-loader": "^6.1.0"
,查了之后都说v6有兼容性问题,所以将版本改为"less-loader": "5.0.0",
,这样就可以了,没有报错。
4.2 speed-measure-webpack-plugin
speed-measure-webpack-plugin
和 HotModuleReplacementPlugin
不能同时使用,否则会报错。所以在开发环境中先把费时分析插件注释,见[webpack.base.js]。
5. 知识点
5.1 redux-thunk
redux-thunk的原理就是判别action的类型,如果action是函数,就调用这个函数。thunk仅仅做了执行这个函数,并不在乎函数主体内是什么,也就是说thunk使得redux可以接受函数作为action,但是函数的内部可以多种多样。比如下面是一个获取商品列表的异步操作所对应的action:
1 | export default ()=>(dispatch)=>{ |
从这个具有副作用的action中,我们可以看出,函数内部极为复杂。如果需要为每一个异步操作都如此定义一个action,显然action不易维护。
action不易维护的原因:
- action的形式不统一
- 就是异步操作太为分散,分散在了各个action中
5.2 redux-saga
从 Saga 内触发异步操作(Side Effect)总是由 yield 一些声明式的 Effect 来完成的。
5.2.1 call
https://redux-saga-in-chinese.js.org/docs/basics/DeclarativeEffects.html
redux-saga 提供了一个不一样的方式来执行异步调用
1 | import { call } from 'redux-saga/effects' |
call 是一个会阻塞的 Effect。即 Generator 在调用结束之前不能执行或处理任何其他事情。
call 不仅可以用来调用返回 Promise 的函数。我们也可以用它来调用其他 Generator 函数。
fork表示无阻塞调用。
5.2.2 take
1 | function* loginFlow() { |
loginFlow 首先等待一个 LOGIN_REQUEST action。 然后在 action 的 payload 中获取有效凭据(即 user 和 password)并调用一个 call 到 authorize 任务。
5.2.3 put
redux-saga做为中间件,工作流是这样的:
UI——>action1————>redux-saga中间件————>action2————>reducer..
从工作流中,我们发现redux-saga执行完副作用函数后,必须发出action,然后这个action被reducer监听,从而达到更新state的目的。相应的这里的put对应与redux中的dispatch,工作流程图如下:
从图中可以看出redux-saga执行副作用方法转化action时,put这个Effect方法跟redux原始的dispatch相似,都是可以发出action,且发出的action都会被reducer监听到。
1 | yield put({type:'login'}) |
5.2.4 select
put方法与redux中的dispatch相对应,同样的如果我们想在中间件中获取state,那么需要使用select。select方法对应的是redux中的getState,用户获取store中的state,使用方法:
1 | const state= yield select() |
5.2.5 fork
fork方法相当于web work,fork方法不会阻塞主线程,在非阻塞调用中十分有用。
当我们 fork 一个 任务,任务会在后台启动,调用者也可以继续它自己的流程,而不用等待被 fork 的任务结束。
5.2.6 takeEvery和takeLatest
takeEvery和takeLatest用于监听相应的动作并执行相应的方法,是构建在take和fork上面的高阶api,比如要监听login动作,takeEvery方法可以:
1 | yield takeEvery('login',loginFunc) |
takeEvery监听到login的动作,就会执行loginFunc方法,除此之外,takeEvery可以同时监听到多个相同的action。在上面的例子中,takeEvery 允许多个 loginFunc 实例同时启动
takeLatest方法跟takeEvery是相同方式调用:
1 | takeLatest('login',loginFunc) |
与takeLatest不同的是,takeLatest是会监听执行最近的那个被触发的action。
在任何时刻 takeLatest 只允许执行一个 loginFunc 任务,并且这个任务是最后被启动的那个,如果之前已经有一个任务在执行,那之前的这个任务会自动被取消。
5.3 webpack
5.3.1 Source Map
SourceMap是一种映射关系。当项目运行后,如果出现错误,我们可以利用sourceMap反向定位到源码。
sourceMap就是一个信息文件,里面储存着打包前的位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置。
相关文章:https://juejin.im/post/6844903971648372743
6. 功能模块
新增独立的功能模块
6.1 采编中心
src/pages/EditorialCenter/Auditing 审模块
自定义useRequest hook
封装统一列表逻辑,包括获取列表数据,分页;其中每个页面组件顶部的搜索条件不唯一,所以这里只将 请求列表数据的接口 + PageIndex + PageSize,进行了封装,各个页面的参数以Object.assign 拼接的方式传入。
6.1.1 自定义hook
1. 列表数据请求 + 分页
src/pages/EditorialCenter/Auditing/auditPending.js
1 | import React, {useMemo} from 'react'; |
/redux/hooks/useRequest.js
1 | import {useState, useEffect} from 'react'; |
2. 列表参数条件查询,添加了日期控件、搜索框
src/pages/EditorialCenter/Auditing/auditPending.js
1 | function getColumns(){ |
3. 这里的state太多,使用 useReducer 改进
1 | import React, {useState, useEffect, useCallback, useReducer} from 'react'; |
将列表页面中的table单独封装如下:
/src/pages/EditorialCenter/components/TableFunction.js
1 | import React from 'react'; |
6.1.2 封装全局按钮loading
暂时,发模块:列表、条件筛选,获取数据都走redux流程
6.2 接入Sentry
这里的内容直接登录的Sentry服务,创建Project即可看到:
Add the Sentry SDK as a dependency using yarn or npm:
yarn add @sentry/react | npm install @sentry/react
Connecting the SDK to Sentry
1
2
3
4
5
6
7
8import React from 'react';
import ReactDOM from 'react-dom';
import * as Sentry from '@sentry/react';
import App from './App';
Sentry.init({dsn: "http://XXXXXXXX"});
ReactDOM.render(<App />, document.getElementById('root'));
接入完成,当项目中发送错误时,会发送信息到Sentry服务,具体请见 [跳转]。
7. Webpack 优化
见webpack.prod.js
文件,查看 new BundleAnalyzerPlugin()
打包分析结果
7.1 按需加载antd
npm install --save-dev babel-import-plugin
打包结果分析:失败,见 3.5
7.2 优化构建速度
7.2.1 Dll
https://jiafei2333.github.io/2019/11/14/Webpack-majorization/ 见 5.DllPlugin && DllReferencePlugin
1 | General output time took 38.24 secs |
构建时能够看到delegated
证明是用引入的dll/
下提前生成的文件,但是看了耗时,时间并没有缩短,反而还增加了……待解决。
7.2.2 include/exclude
[使用include]
1 | { |
没有配置include
时,打包耗时如下:
配置了include
后,打包耗时如下:
可以看出构建时间大大缩短了,特别是babel-loader
的时间,打包时的babel-loader
耗时同样由20s+缩短为3s+,大大加快了打包时间。
[使用exclude]
1 | { |
配置了exclude
后,构建耗时如下:
7.3 SplitChunks
1 | optimization:{ |
打包之后发现效果不明显,可能是这个项目代码太少了….待解释。
8. 配置文件
8. 相关文章
- https://juejin.im/post/5b4de4496fb9a04fc226a7af
- https://medium.com/stashaway-engineering/react-redux-tips-better-way-to-handle-loading-flags-in-your-reducers-afda42a804c6
- https://juejin.im/post/5b440f7ae51d45195759f345
- https://juejin.im/post/5d6771375188257573636cf9
- [Webpack 优化]
9. 未完成功能
- 1.loading
列表页loading(完成,封装的自定义hook)组件动态加载loading(完成)- 按钮loading
- 2.nprogress
- 3.
saga代替thunk(完成) - 4.
配置node后台服务(完成) - 5.
antd中局部修改默认样式(完成 不需要global包裹) - 6.
react-hot-loader(完成) - 7.
强制刷新时获取基本信息(菜单权限、配置信息等等)(完成) - 8.
路由监听,拼接路由(完成) - 9.
自定义hooks封装(完成) - 10.
动态加载组件(react-loadable)(完成) - 11.
打包配置-打包分析-打包优化-查看bundle大小(完成) - 12.主题色配置
- 13.
打包问题、引入样式问题(完成,开启css module) - 14.
Source Map配置+生产环境下配合Sentry(完成) - 15.
生产环境打包时拷贝web.config文件(完成) - 16.登录判断 PrivateRoute
- 17.
图片打包优化,小图片生成base64等(完成,将图片放到样式中用url-loader) - 18.生产环境下去掉state状态输出
github项目入口: https://github.com/jiafei2333/React-Redux-Antd
tips:
react-template添加配置
- webpack.prod.js
- webpack.base.js
- Loading组件和loading图
- babel 和 package.json
- webpack.dll.js