1. 程式人生 > >react通過react-router-dom攔截實現登入驗證

react通過react-router-dom攔截實現登入驗證

在使用react開發專案中,有些頁面需要登入之後才能訪問,所以需要進行攔截,此處分享採用react-router-dom v4+redux+redux-saga+ant-mobile+axios技術來實現

Login.jsx

import React from "react";
import { is, fromJS } from "immutable";
import { connect } from "react-redux";
import { PropTypes } from "prop-types";
import { login } from "../../store/login/action";
import { InputItem, Button, List } from "antd-mobile";

class Login extends React.Component {
  static propTypes = {
    loginInfo: PropTypes.object.isRequired,
    login: PropTypes.func.isRequired
  };
  constructor(props) {
    super(props);
    this.state = { name: "", psd: "" };
  }
  //antd-mobile的InputItem必須用List元件包裹,其輸入值就是value
  username = value => {
    this.setState({
      name: value
    });
  };
  password = value => {
    this.setState({
      psd: value
    });
  };
  toLogin = () => {
    if (this.state.name === "") {    
        // to do sth
    } else if (this.state.psd === "") {
      // to do sth
    } else {
      let data = {};
      data.username = this.state.name;
      data.password = this.state.psd;
    // 觸發action
      this.props.login(data);
    }
  };
  componentWillReceiveProps(nextProps) {
      if (Object.keys(nextProps.loginInfo).length > 0) {
        if (nextProps.loginInfo.result.code === 10000) {
          // 登入成功
          sessionStorage.setItem(
            "userinfo",
            JSON.stringify(nextProps.loginInfo.data[0])
          );
          setTimeout(() => {
            let RedirectUrl = nextProps.location.state
              ? nextProps.location.state.from.pathname
              : "/";
            nextProps.history.push(RedirectUrl);
          }, 200);
        } else {

        }
      }
  }
  shouldComponentUpdate(nextProps, nextState) {
    return (
      !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
    );
  }
  componentWillUpdate(nextProps, nextState) {}
  componentWillMount() {}
  render() {
    return (
      <div >
        <div>
          <List>
            <InputItem
              type="text"
              placeholder="請輸入使用者名稱"
              onChange={this.username}
              value={this.state.name}
            />
            <InputItem
              type="password"
              placeholder="請輸入密碼"
              onChange={this.password}
              value={this.state.psd}
            />
          </List>

          <Button type="primary" onClick={this.toLogin}>登入</Button>
        </div>
      </div>
    );
  }
  componentDidMount() {}
  componentDidUpdate() {}
  componentWillUnmount() {}
}

export default connect(
  state => ({
    loginInfo: state.loginInfo.userinfo
  }),
  { login }
)(Login);

utils/asyncCompontent.jsx

import React from 'react'
export default function asyncComponent(importComponent){
    class AsyncComponent extends React.Component{
        constructor(props){
            super(props);
            this.state = {
                component:null
            };
        }
        async componentDidMount(){
            const {default:component} = await importComponent();
            this.setState({component});
        }
        render(){
            const C = this.state.component;
            return C ? <C {...this.props}/> : null;
        }
    }
    return AsyncComponent;
}

router/index.js

import React from "react";
import { HashRouter, Route, Redirect, Switch } from "react-router-dom";
import { PrivateRoute } from "./auth"; //需要登入的路由
import asyncComponent from "../utils/asyncComponent";//按需載入
import app from "../App";
const example = asyncComponent(() =>import("../components/antiDesign/example"));
const login = asyncComponent(() => import("../components/login/login"));
const noMatch = asyncComponent(() => import("../components/noMatch/noMatch"));

class RouteConfig extends React.Component {
  render() {
    return (
      <HashRouter>
        <Switch>
          <Route path="/" exact component={app} />
          <PrivateRoute path="/example" component={example} />
          <Route path="/login" component={login} />
          <Route component={noMatch} />
        </Switch>
      </HashRouter>
    );
  }
}

export default RouteConfig;

router/auth.js

import React, { Component } from "react";
import { connect } from "react-redux";
import { Route, Redirect } from "react-router-dom";

export const PrivateRoute = ({ component: ComposedComponent, ...rest }) => {
  class Authentication extends Component {
    render() {
      let isLogin= this.props.isLogin
        ? this.props.isLogin
        : sessionStorage.getItem("userinfo")
          ? sessionStorage.getItem("userinfo")
          : "";
      return (
        <Route
          {...rest}
          render={props =>
            !isLogin? (
              <Redirect
                to={{
                  pathname: "/login",
                  state: { from: props.location }
                }}
              />
            ) : (
              <ComposedComponent {...props} />
            )
          }
        />
      );
    }
  }

  const AuthenticationContainer = connect(state => ({
    isLogin: state.loginInfo.isLogin
  }))(Authentication);
  return <AuthenticationContainer />;
};

store/store.js

import { createStore, combineReducers, applyMiddleware } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import createSagaMiddleware from 'redux-saga'
import logger from 'redux-logger'
import * as login from './login/reducer'
import rootSaga from './sagas'
const sagaMiddleware = createSagaMiddleware();
const middlewares = [ sagaMiddleware, logger];
let store = createStore(
    combineReducers(login),
    composeWithDevTools(applyMiddleware(...middlewares))
);
sagaMiddleware.run(rootSaga);
export default store;

store/sagas/index.js

import { takeEvery, takeLatest, call, put, all } from 'redux-saga/effects';
import * as loginPro from '../login/action-types';
import {  login } from '../../service/api';

// worker saga
// Login
function* getLoginInfo(action) {
    try {
        const userInfo = yield call(login, action.param);
        if (userInfo.data.result.code === 10000) {
            yield put({ type: loginPro.LOGIN_INFO, userinfo: userInfo.data, isLogin: true })
        } else {
            yield put({ type: loginPro.LOGIN_INFO, userinfo: userInfo.data, isLogin: false })
        }

    } catch (e) {
        yield put({ type: loginPro.LOGIN_FAILIURE, error: e })
    }
}

// wacther saga
function* takeLogin() {
    yield takeLatest(loginPro.LOGIN_SUCCESS, getLoginInfo)
}



// root saga
export default function* rootSaga() {
    yield takeLogin()
}

store/login/action-types.js

export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
export const LOGIN_INFO = 'LOGIN_INFO';
export const LOGIN_FAILIURE = 'LOGIN_FAILIURE';

store/login/action.js

import * as pro from './action-types'

export const login = (param) => {
    return (dispatch) => dispatch({
        type: pro.LOGIN_SUCCESS,
        param
    })
}

store/login/reducers.js

import * as pro from './action-types'
let defaultState = {
    userinfo: {},
    error: {}
}
export const loginInfo = (state = defaultState, action) => {
    switch (action.type) {
        case pro.LOGIN_INFO:
            return {...state, ...action }
        case pro.LOGIN_FAILIURE:
            return {...state, ...action }
        default:
            return state;
    }
}

service/api.js

import {instance} from './apiConfig';

export const login = (data) => {
    return instance.post('/login', data)
}

service/apiConfig.js

import axios from 'axios';
import Qs from 'qs';
import { Toast } from 'antd-mobile'
// 全域性預設配置

// 設定 POST 請求頭
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

// 在向伺服器傳送前,修改請求資料(只能用在 'PUT', 'POST' 和 'PATCH' 這幾個請求方法)
// 採用Qs的方式是為了用於application/x-www-form-urlencoded
axios.defaults.transformRequest = [(data) => { return Qs.stringify(data) }]


// 在傳遞給 then/catch 前,允許修改響應資料
// axios.defaults.transformResponse = [(data) => { return JSON.parse(data) }]

// 配置 CORS 跨域
// 表示跨域請求時是否需要使用憑證
axios.defaults.withCredentials = true;

axios.defaults.crossDomain = true;

// 設定超時
axios.defaults.timeout = 40000;


// 攔截器的說明
// 1、interceptor必須在請求前設定才有效。
// 2、直接為axios全域性物件建立interceptor, 會導致全域性的axios發出的請求或接收的響應都會被攔截到, 所以應該使用axios.create() 來建立單獨的axios例項。

let instance = axios.create({
        baseURL: '',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        withCredentials: true,
    })


// Add a request interceptor
instance.interceptors.request.use(function(config) {

    // POST 請求引數處理成 axios post 方法所需的格式        
    if (config.method === 'post' || config.method === "put" || config.method === "delete") {
        //config.data = JSON.stringify(config.data);
    }
    Toast.loading('Loading...', 0, () => {
        console.log('Load complete !!!');
    });
    return config;
}, function(error) {
    // Do something with request error
    Toast.hide()
    return Promise.reject(error);
});

// Add a response interceptor
instance.interceptors.response.use(function(response) {
    // Do something with response data
    Toast.hide()
    return response.data;
}, function(error) {
    // Do something with response error
    Toast.hide()
    return Promise.reject(error);
});



export { instance };

index.js

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import store from "./store/store";
import Route from "./router/";
import registerServiceWorker from "./registerServiceWorker";
import { AppContainer } from "react-hot-loader";


const render = Component => {
  ReactDOM.render(
    //繫結redux、熱載入
    // Provider作為頂層元件,提供資料來源,然後可以源源不斷的從它向下流到各級子孫節點上去,所以Redux把store註冊到Provider中
    // Provider接受一個屬性store,這就是我們全域性的資料store。我們要根據reducer函式來建立它
    <Provider store={store}>
      <AppContainer>
        <Component />
      </AppContainer>
    </Provider>,
    document.getElementById("root")
  );
};

render(Route);

// Webpack Hot Module Replacement API
if (module.hot) {
  module.hot.accept("./router/", () => {
    render(Route);
  });
}
registerServiceWorker();