搭建项目框架
新建项目
执行如下代码, 用 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
项目源代码
GitHub 仓库
来源: https://www.cnblogs.com/detectiveHLH/p/10128948.html