搭建項目框架
新建項目
執行如下代碼,用create-react-app
來建立項目的基礎框架,然后安裝需要用到的依賴。
$ npx create-react-app my-test-project
$ cd my-test-project
$ yarn add react-router-dom react-redux prop-types redux redux-saga
$ yarn start
完成后,應用啟動在localhost的3000端口。
接入react-router-dom
react-router-dom其實就是react-router 4.0,與之前的3.0有什么區別呢?react-router被一分為三。react-router
、react-router-dom
和react-router-native
。
react-router
實現了路由的核心的路由組件和函數。而react-router-dom
和react-router-native
則是基於react-router
,提供了特定的環境的組件。
react-router-dom
依賴react-router
,安裝的時候,不用再顯示的安裝react-router
, 如果你有機會去看react-router-dom的源碼,就會發現里面有些組件都是從react-router
中引入的。
新建layout
在/src
下新建layout目錄。為什么要新建layout目錄,因為有可能我們會用到多個layout,layout是一個什么樣的概念?
例如這個應用需要提供一部分功能在微信使用。那么進入所有微信的相關界面下都要進行鑒權。沒有鑒權信息就不允許訪問,但是這個服務仍然有所有人都可以訪問的路由。使用layout可以很好的幫我們解決這個問題。
將所有的需要鑒權的頁面放在例如WechatContainer
下,只有在有微信相關鑒權的信息存在,才允許訪問接下來的界面,否則,容器內甚至可以直接不渲染接下來的界面。
在/src/layout
下新建兩個文件,分別是AppLayout.js
、WechatLayout.js
。
AppLayout.js
的代碼如下。在這個layout中,首頁就是單純的一個路由,導向至首頁。而接下來的/wechat
則是把路由導向至了一個微信端專用的layout。
import React, { Component } from 'react';
import Home from '../routes/home';
import WechatLayout from './WechatLayout';
import { Route, Switch } from 'react-router-dom';
/**
* 項目入口布局
* 在此處根據一級路由的不同進入不同的container
* 每個container有自己不同的作用
*
* 在react-router V4中,將原先統一在一處的路由分散到各個模塊中,分散到各個模塊當中
* 例如: WechatLayout的路由為/wechat 表示到該layout下的默認路徑
*/
class AppLayout extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<div className='App'>
<main>
<Switch>
<Route path='/' exact component={Home} />
<Route path='/wechat' component={WechatLayout} />
</Switch>
</main>
</div>
);
}
}
export default AppLayout;
WechatLayout.js
的代碼如下。在這個layout中,我們就可以對訪問該路由的用戶進行鑒權。如果沒有權限,我們可以直接限制用戶的訪問,甚至直接不渲染render中的數據。
例如,我們可以在componentWillMount
中或者在render中,根據當前的state數據,對當前用戶進行鑒權。如果沒有權限,我們就可以將當前頁面重定向到沒有權限的提示界面。
import React, { Component } from 'react';
import Home from '../routes/wechat/home';
import { Route, Switch } from 'react-router-dom';
import { connect } from 'react-redux';
class WechatLayout extends Component {
constructor(props) {
super(props);
this.state = {};
}
componentWillMount() {
}
render() {
const className = 'Wechat-Layout';
return (
<div className={`${className}`}>
<header>
Our Manage Layout
</header>
<main>
<Switch>
<Route path={`${this.props.match.path}/home`} component={Home} />
</Switch>
</main>
</div>
);
}
}
const mapStateToProps = state => ({
reducer: state.wechatLayout
});
export default connect(mapStateToProps)(WechatLayout);
新建routes
新建/src/routes/home/index.js
,代碼如下。
import React, { Component } from 'react';
import {Link} from 'react-router-dom';
class Home extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
const className = 'Home';
return (
<div className={`${className}`}>
<h1>This is Home</h1>
<div><Link to={'/wechat/home'}>Manage Home</Link></div>
</div>
);
}
}
export default Home;
新建/src/routes/wechat/home/index.js
, 代碼如下。在代碼中可以看到,觸發reducer很簡單,只需要調用dispatch方法即可。dispatch中的payload就是該請求所帶的參數,該參數會傳到saga中間層,去調用真正的后端請求。並在請求返回成功之后,調用put
方法更新state。
import React, { Component } from 'react';
import {connect} from "react-redux";
class Home extends Component {
constructor(props) {
super(props);
this.state = {};
}
componentWillMount() {
this.props.dispatch({ type: 'WATCH_GET_PROJECT', payload: { projectName: 'tap4fun' } });
}
render() {
const className = 'Wechat-Home';
return (
<div className={`${className}`}>
<h1>Home</h1>
<h2>The project name is : { this.props.reducer.projectName }</h2>
</div>
);
}
}
const mapStateToProps = state => ({
reducer: state.wechat
});
export default connect(mapStateToProps)(Home)
新建container
在/src
下新建container
,在container
中新建文件AppContainer.js
。我們整個react應用都裝在這個容器里面。AppContainer.js
的代碼如下。
而其中的Provider組件,將包裹我們應用的容器AppLayout
包在其中,使得下面的所有子組件都可以拿到state。Provider
接受store參數作為props,然后通過context往下傳遞。
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';
import AppLayout from '../layout/AppLayout';
class AppContainer extends Component {
constructor(props) {
super(props);
this.state = {};
}
static propTypes = {
store: PropTypes.object.isRequired
};
render() {
const { store } = this.props;
return (
<Provider store={store}>
<Router>
<AppLayout />
</Router>
</Provider>
);
}
}
export default AppContainer;
修改項目入口文件
更新/src/index.js
,代碼如下。在此處會將create出來的store容器當作屬性傳入到Appcontainer中,作為我們應用的狀態容器。
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import * as serviceWorker from './serviceWorker';
import AppContainer from './container/AppContainer';
import createStore from './store/createStore';
const store = createStore();
ReactDOM.render(<AppContainer store={store} />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();
新建store
新建/src/store/craeteStore.js
,代碼如下。通過以下的方式,我們可以給redux添加很多中間件,甚至是自己寫的中間件。
比如,我們可以自己實現一個日志中間件,然后添加到中間件數組middleWares
中,在創建redux的store的時候,應用我們自己寫的中間件。
import { applyMiddleware, compose, createStore } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootReducer from '../reducers';
import rootSaga from '../saga';
export default function configureStore(preloadedState) {
// 創建saga中間件
const sagaMiddleware = createSagaMiddleware();
const middleWares = [sagaMiddleware];
const middlewareEnhancer = applyMiddleware(...middleWares);
const enhancers = [middlewareEnhancer];
const composedEnhancers = compose(...enhancers);
// 創建存儲容器
const store = createStore(rootReducer, preloadedState, composedEnhancers);
sagaMiddleware.run(rootSaga);
return store;
}
在這引入了redux-saga
。我之前在使用redux的時候,幾乎在每個模塊都要寫相應的action和reducer,然后在相應的模塊文件中引入action的函數,然后在使用mapDispatchToProps
將該函數注入到props中,在相應的函數中調用。並且,一個action不能復用,即使觸發的是相同的reducer。這樣就會出現很多重復性的代碼,新增一個模塊的工作也相對繁瑣了很多。
但是使用了redux-saga
之后,只需要在reducer中定義好相應類型的操作和saga就可以了。不需要定義action的函數,不需要在文件中引入action中函數,甚至連mapDispatchToProps
都不需要,直接使用this.props.dispatch({ 'type': 'WATCH_GET_PROJECT' })
就可以調用。而且,action可以復用。
新建saga
新建/src/saga/index.js
,代碼如下。
import { put, takeEvery } from 'redux-saga/effects';
import { delay } from 'redux-saga';
export function* fetchProject() {
yield delay(1000);
yield put({ type: 'GET_PROJECT' })
}
export default function * rootSaga() {
yield takeEvery('WATCH_GET_PROJECT', fetchProject);
}
新建reducer
新建/src/reducers/wechat.js
,代碼如下。
const initialState = {
projectName: null
};
export default function counter(state = initialState, action) {
let newState = state;
switch (action.type) {
case 'GET_PROJECT':
newState.projectName = action.payload.projectName;
break;
default:
break;
}
return {...newState}
}
新建/src/reducers/index.js
,代碼如下。
import { combineReducers } from 'redux';
import Wechat from './wechat';
export default combineReducers({
wechat: Wechat
});
在這里我們使用了combineReducers
。在之前的基於redux的應用程序中,常見的state結構就是一個簡單的JavaScript對象。
重新啟動應用
到此處,重新啟動應用,就可以在http://localhost:3000/wechat/home
下看到從reducer中取出的數據。
在頁面中,我們就可以通過代碼this.props.dispatch
的方式,來觸發action。
參考
- https://github.com/mrdulin/blog/issues/42
- https://cn.redux.js.org/docs/recipes/reducers/UsingCombineReducers.html