详情可参阅
package.json
javascript
项目说明:
css
1.路由懒加载vue
2.错误路由匹配404页面java
3.Api请求封装Axios工具类react
4.redux-saga处理异步请求webpack
5.nprogress加载条ios
6.路由鉴权git
7.装饰器模式状态共享、表单包裹github
8.在redux中加入路由跳转功能web
7.接口是java服务端的Api,暂时就不抽出来了,因此项目是没法正常登录的,项目会提供UI视觉稿,有对应需求的能够借阅参考。最后说一句,redux-saga真香~~~
本项目用的是yarn管理依赖包,须要安装yarn
yarn install //安装依赖
yarn start //运行
复制代码
.
├─ config/ # Webpack 配置目录
├─ public/ # 模板文件
├─ dist/ # build 生成的生产环境下的项目
├─ scripts/ # Webpack环境变量配置
├─ src/ # 源码目录(开发都在这里进行)
│ ├─ assets/ # 放置须要经由 Webpack 处理的静态文件
│ ├─ components/ # 组件
│ │ ├─ Layout/ # 全局布局
│ │ ├─ PrivateRoute/ # 路由守卫
│ ├─ store/ # Redux-sagas
│ │ ├─ actions/ # (Actions)
│ │ ├─ reducers/ # (Reducers)
│ │ ├─ sagas/ # (Sagas)
│ │ ├─ index.js # (Store文件管理)
│ ├── router/ # 路由(ROUTE)
│ ├── service/ # 服务(SERVICE,统一Api管理)
│ ├── utils/ # 工具库
│ ├── pages/ # 视图页(pages)
│ ├── index.js # 启动文件
│ ├── App.js # 主入口页
├── .gitignore # (配置)需被 Git 忽略的文件(夹)
├── package.json
复制代码
暂未加入测试工具
yarn build
build以后,若是想要在本地线上环境打开的话,建议先安装一个http-server本地服务器
npm install http-server -g
安装成功以后,直接在要访问的build文件夹中运行http-server
命令便可打开本地服务环境。也可自行配置端口,具体命令可参考http-server
在build目录下开启本地服务器后,有可能打开本项目是空白的,资源加载不出来,只要在package.json里面配置homepage
属性就行了
//package.json 文件增长配置
"homepage": ".",
复制代码
入口文件主要定义路由页面,由于是此项目是单页面应用,因此主入口只要配置三大模块路由便可,根路由地址/
匹配<IndexLayout />
,login
匹配<Login />
,404
匹配<ErrorPage />
为了让路由懒加载,引入路由的时候能够用异步组件包裹一层Component,为了方便加载时查找到对应的文件名,能够设置/* webpackChunkName: "name" */
便可,以下
// 该文件为实现相似github页面加载的那个加载条
import LoadableComponent from '@/utils/LoadableComponent'
const Login = LoadableComponent(()=>import(/* webpackChunkName: "login" */ '@/pages/Login'))
复制代码
这里引用的是HashRouter
路由模式,BrowserRouter模式须要后台配合,不然打包的时候在当前路由地址刷新时会形成空白的错误。
入口文件App.js
的所有代码贴一下吧
import React, { Component } from 'react'
import IndexLayout from '@/components/Layout/index'
import { connect } from 'react-redux';
import LoadableComponent from '@/utils/LoadableComponent'
import { HashRouter as Router, Switch, Route, Redirect } from 'react-router-dom'
const Login = LoadableComponent(()=>import(/* webpackChunkName: "login" */ '@/pages/Login'))
const ErrorPage = LoadableComponent(()=>import(/* webpackChunkName: "errorPage" */ '@/pages/ErrorPage'))
//装饰器模式连接Redux数据,省略不少复杂代码,真香。注意:不要的对象能够传个null
@connect(
state => ({
id_token: state.loginReducer.id_token
})
)
class App extends Component {
render() {
return (
<Router>
<Switch>
//此处重定向的地址为登陆后的首页面地址
<Route exact path="/" render={ () => <Redirect to="/apply" push /> } />
//此处404页面只是声明路由地址,暂未匹配路由
<Route path="/404" component={ ErrorPage } />
//路由鉴权,若是没有id_token则跳转到登陆页
<Route path="/login" render={() => {
return this.props.id_token ? <Redirect to="/" /> : <Login />
}} />
//登陆后的主模板组件
<Route render={ () => <IndexLayout /> } />
</Switch>
</Router>
)
}
}
export default App
复制代码
该项目为左右布局,没有Header,Footer。界面比较创新...
import React, { Component } from 'react'
import ContentMain from '@/components/Layout/ContentMain' //主内容组件
import SliderNav from '@/components/Layout/SliderNav' //菜单栏组件
import { Layout } from 'antd'
const { Content, Sider } = Layout;
class IndexLayout extends Component {
render() {
return (
<Layout>
<Sider
collapsible
trigger={null}
>
<SliderNav/>
</Sider>
<Layout>
<Content style={{background: '#f7f7f7'}}>
<ContentMain/>
</Content>
</Layout>
</Layout>
)
}
}
export default IndexLayout
复制代码
菜单栏的代码就不一一张贴了,内容有点长,菜单栏的路由栏目有留子菜单的入口配置,只要对应的路由数组按照格式配置好便可。全部的导航菜单布局
在Components
组件库中的Layout
文件夹目录下。这里提供一下导航栏的主路由配置信息。注意,这里的路由配置跟以前的路由配置不是同一个信息,前者是总路由地址,这里的路由是右边Content
的全部路由信息。
import React, { Component } from 'react'
import { withRouter, Switch, Redirect, Route } from 'react-router-dom'
import LoadableComponent from '@/utils/LoadableComponent'
//路由鉴权组件,包裹全部`Content`的页面,若是token失效,则跳回`Login页面`
import PrivateRoute from '@/components/PrivateRoute'
const Apply = LoadableComponent(()=>import(/* webpackChunkName: "apply" */ '@/pages/Apply'))
const Case = LoadableComponent(()=>import(/* webpackChunkName: "case" */ '@/pages/Case'))
@withRouter
class ContentMain extends Component {
render () {
return (
<div style={{padding: '20px 32px'}}>
<Switch>
<PrivateRoute exact path='/apply' component={ Apply }/>
<PrivateRoute exact path='/case' component={ Case }/>
//404页面在这里匹配路由,就能正确匹配错误路由了
<Route render={ () => <Redirect to="/404" /> } />
<Redirect exact from='/' to='/apply'/>
</Switch>
</div>
)
}
}
export default ContentMain
复制代码
在决定用Redux-saga
以前,也有考虑引用Redux-thunk
来作状态共享,毕竟Redux-thunk
上手要简单不少。可是Redux-thunk
同步异步代码都要下在同一个文件里面,若是单页面接口过多的话,会形成面条式的代码,也不利于理解和维护。因此毅然决定引用Redux-saga
。中间碰到不少的坑,由于在Github上不多能发现Redux-saga
比较完整系列能借阅的项目,每次卡住都是各类翻阅资料。如下会罗列出在开发过程当中容易遇到的问题,让参阅本项目的朋友们能少踩点坑...
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware from 'redux-saga';
import reducer from './reducers';
import sagas from './sagas'
import { routerMiddleware } from 'react-router-redux';
const sagaMiddleware = createSagaMiddleware();
const createHistory = require('history').createHashHistory;
const history = createHistory(); // 初始化history
const routerWare = routerMiddleware(history);
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware, routerWare));
const store = createStore(
reducer,
enhancer
)
sagaMiddleware.run(sagas)
export default store
复制代码
这里引入的sagas
是按页面引用分类的saga
文件,避免全部异步请求都写在同一个文件中。由于本项目只有三大模块,登陆页、申请页、案件页,因此分三大模块就行了。
// saga模块化引入
import { fork, all } from 'redux-saga/effects'
// 异步逻辑
import { loginSagas } from './login'
import { applySagas } from './apply'
import { caseSagas } from './case'
// 单一进入点,一次启动全部Saga
export default function* rootSaga() {
yield all([
fork(loginSagas),
fork(applySagas),
fork(caseSagas)
])
}
复制代码
redux-saga
的正确使用在这里我就不作过多阐述了,想要引用的同窗能够去看看官方文档的介绍。或者借阅本项目的源代码,依葫芦画瓢,多写几遍天然就多会了。不过,在此的前提是,你首先得去了解一遍Es6的Generator函数
。
具体的redux-saga
异步引用在项目中不少地方有应用,等时间充裕了我补一遍redux-saga
的使用教程。
这里说两个引用redux-saga
容易遇到的坑
react
和vue
的路由跳转有点不一致,vue
是封装了全部的路由信息,只要你引用了vue-router
,你就可为所欲为的引用路由跳转。可是react
不同,在js文件
中若是引用了react-router
后能够直接引用<Link></Link>
或者this.props.history.push('/login')
的方式跳转路由。可是引用了redux-saga
状态共享后,当异步请求结束以后再跳转路由信息是再正常不过的需求了,但这里this.props.history.push('/login')
这种方式是不生效的,想要在状态共享中跳转路由,须要额外配置。
1.首先安装history
和react-router-redux
yarn add history react-router-redux
2.在store
里面引用
import createSagaMiddleware from 'redux-saga';
import { routerMiddleware } from 'react-router-redux';
const sagaMiddleware = createSagaMiddleware();
const createHistory = require('history').createHashHistory;
const history = createHistory(); //初始化history
const routerWare = routerMiddleware(history);
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
//这里是包裹两个中间件,saga和状态共享路由的中间件,想要在saga中跳转页面,routerWare这个中间件是必不可少的!!!
const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware, routerWare));
复制代码
3.在saga
中应用
import { push } from 'react-router-redux';
function* login() {
if (...) {// 登陆成功,路由跳转
yield put(push('/login')) //Generator指令跳转
}
}
复制代码
其实形成这种缘由最主要仍是出于对Generator指令
不熟悉的缘故 以下代码,每一个Generator方法内都要加一个while(true){}
,配合多个Generator
方法yield all([])
监听,这样就能作到每次一个saga
任务请求结束后,下次再进来(例如分页)
同一个请求时,每次都会从新监听,而后再继续进入Generator函数
内,这样就不会一个接口就只请求一次。
这个细微的小Bug真的是让我吃了不小的苦头啊...
function * getSearchRequest() {
while(true){ //保持监听链接
const resData = yield take(types.GET_SEARCH_DATA);
const response = yield call(seachData, resData.payload)
yield put(getSearchDataSuccess(response))
}
}
function * getDetailRequest() {
while(true){
const resData = yield take(types.GET_DRAFT_DETAIL_REQUEST);
const response = yield call(searchDetail, resData.payload)
yield put(getDetailSuccess(response));
}
}
export function * caseSagas() {
yield all([
fork(getSearchRequest),
fork(getDetailRequest)
]);
}
复制代码
antd
本项目引用的是Antd的UI组件库,一次性打包加载全部的组件固然是过于臃肿,官网给的按需加载配置建议是在项目yarn run eject
以前的,明显不符合绝大多数线上代码的一个定制化配置,因此要配置antd
按需加载,还得另行配置
这里我直接把babel全部的配置都放上吧,解决两个知识点。antd按需加载和装饰器模式配置。
装饰器模式要先安装依赖,而后配置babel
yarn add babel-plugin-transform-decorators-legacy
//package.json
"babel": {
"plugins": [
[
"@babel/plugin-proposal-decorators", //引用@connect、@withRouter装饰器模式必须配置babel
{
"legacy": true
}
],
[
"import", //antd按需加载配置
{
"libraryName": "antd",
"libraryDirectory": "es",
"style": true //这里若是设置为true的话则为自定义主题,不然就是加载所有antd css样式
}
]
]
}
复制代码
如上,若是antd
按需加载配置的style
属性为true
的话,那自定义主题配置可还没结束,继续...
在webpack.config.js
文件中,
// common function to get style loaders
const getStyleLoaders = (cssOptions, preProcessor) => {
const loaders = ...
if (preProcessor) {
let loader = {
loader: require.resolve(preProcessor),
options: {
sourceMap: true,
},
}
if (preProcessor === "less-loader") {
//如下为antd的全部主题配色,更多的变量可访问
//https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less
loader.options.modifyVars = {
'primary-color': '#C89F64', //主题颜色
'link-color': '#1DA57A', //主题link颜色
'border-radius-base': '2px'
}
loader.options.javascriptEnabled = true
}
loaders.push(
{
loader: require.resolve('resolve-url-loader'),
options: {
sourceMap: isEnvProduction && shouldUseSourceMap,
},
},
loader
);
}
return loaders;
};
复制代码
整个项目实际上是已经开发完毕的,惟一的遗憾是API请求没法开放,只能提供参考。 如下我会附一个Github的源码,包括设计稿,拿着UI看源码就不那么费劲了。若是遇到同模块能引用的,it`s my pleasure~~~
附:React是彻底的组件化开发概念,万物皆组件,在作这个项目时,时间太紧,这个项目就花了六七个工做日,不包括测试时间。因此代码耦合仍是有点严重的,见谅哈~ 等后期时间充裕了会再加上Immutable.js
,并结合React-hooks优化部分模块,让它更加有逼格些...嘿哈。
最后,未完待续。。。