zoukankan      html  css  js  c++  java
  • 从无到有-在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-routerreact-router-domreact-router-native

    react-router实现了路由的核心的路由组件和函数。而react-router-domreact-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.jsWechatLayout.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。

    参考

    项目源代码

    Github仓库

  • 相关阅读:
    Cookie和Session的那些事儿
    【IScroll深入学习】解决IScroll疑难杂症
    使用 iscroll 实现焦点图无限循环
    jquery.mobiscroll仿Iphone ActionSheet省市区联动
    mobiscroll之treelist使用
    jQuery.extend 函数详解
    jQuery插件开发精品教程,让你的jQuery提升一个台阶
    用js实现动画效果核心方式
    js特效,加速度,图标跳动
    详解用CSS3制作圆形滚动进度条动画效果
  • 原文地址:https://www.cnblogs.com/detectiveHLH/p/10128948.html
Copyright © 2011-2022 走看看