搭建项目框架
新建项目
执行如下代码,用 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/bl…
https://cn.redux.js.org/docs/…
项目源代码
Github 仓库