执行以下代码,用create-react-app
来创建项目的基础框架,而后安装须要用到的依赖。javascript
$ 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端口。css
react-router-dom其实就是react-router 4.0,与以前的3.0有什么区别呢?react-router被一分为三。react-router
、react-router-dom
和react-router-native
。html
react-router
实现了路由的核心的路由组件和函数。而react-router-dom
和react-router-native
则是基于react-router
,提供了特定的环境的组件。java
react-router-dom
依赖react-router
,安装的时候,不用再显示的安装react-router
, 若是你有机会去看react-router-dom的源码,就会发现里面有些组件都是从react-router
中引入的。react
在/src
下新建layout目录。为何要新建layout目录,由于有可能咱们会用到多个layout,layout是一个什么样的概念?git
例如这个应用须要提供一部分功能在微信使用。那么进入全部微信的相关界面下都要进行鉴权。没有鉴权信息就不容许访问,可是这个服务仍然有全部人均可以访问的路由。使用layout能够很好的帮咱们解决这个问题。github
将全部的须要鉴权的页面放在例如WechatContainer
下,只有在有微信相关鉴权的信息存在,才容许访问接下来的界面,不然,容器内甚至能够直接不渲染接下来的界面。redux
在/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);
新建/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)
在/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();
新建/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能够复用。
新建/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); }
新建/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。