詳情可參閱
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優化部分模塊,讓它更加有逼格些...嘿哈。
最後,未完待續。。。