react通過react-router-dom攔截實現登入驗證
阿新 • • 發佈:2019-01-24
在使用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();