從無到有-在create-react-app基礎上接入react-router、redux-saga
搭建專案框架
新建專案
執行如下程式碼,用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 createSagaMiddlewarefrom 'redux-saga'; import rootReducer from '../reducers'; import rootSagafrom '../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/">JavaScript物件。
重新啟動應用
到此處,重新啟動應用,就可以在http://localhost:3000/wechat/home
下看到從reducer中取出的資料。
在頁面中,我們就可以通過程式碼this.props.dispatch
的方式,來觸發action。
參考
- ofollow,noindex" target="_blank">https://github.com/mrdulin/blog/issues/42
- https://cn.redux.js.org/docs/recipes/reducers/UsingCombineReducers.html
專案原始碼
Github倉庫