從無到有-在create-react-app基礎上接入react-router、redux-saga

搭建項目框架

新建項目

執行以下代碼,用create-react-app來創建項目的基礎框架,而後安裝須要用到的依賴。css

$ 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端口。html

接入react-router-dom

react-router-dom其實就是react-router 4.0,與以前的3.0有什麼區別呢?react-router被一分爲三。react-routerreact-router-domreact-router-nativereact

react-router實現了路由的核心的路由組件和函數。而react-router-domreact-router-native則是基於react-router,提供了特定的環境的組件。git

react-router-dom依賴react-router,安裝的時候,不用再顯示的安裝react-router, 若是你有機會去看react-router-dom的源碼,就會發現裏面有些組件都是從react-router中引入的。github

新建layout

/src下新建layout目錄。爲何要新建layout目錄,由於有可能咱們會用到多個layout,layout是一個什麼樣的概念?redux

例如這個應用須要提供一部分功能在微信使用。那麼進入全部微信的相關界面下都要進行鑑權。沒有鑑權信息就不容許訪問,可是這個服務仍然有全部人均可以訪問的路由。使用layout能夠很好的幫咱們解決這個問題。後端

將全部的須要鑑權的頁面放在例如WechatContainer下,只有在有微信相關鑑權的信息存在,才容許訪問接下來的界面,不然,容器內甚至能夠直接不渲染接下來的界面。數組

/src/layout下新建兩個文件,分別是AppLayout.jsWechatLayout.jsbash

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倉庫

相關文章
相關標籤/搜索