Йо-йо! Недавно (июнь 2019) доделывал сайт для бонусной системы одной сети пиццерий в своём городе. Для хранения данных я использовал redux. Авторизация или регистрация использовали один из action creator’ов и так мои компоненты могли понять, что пользователь зарегистрирован или авторизирован на сайте.
Это такой глупый компонент, в который мы передаём true или false, и он либо рендерит какой-то компонент, либо делает redirect.
Вот мой PrivateRoute:
import React from 'react'; import { Route, Redirect } from 'react-router-dom'; const PrivateRoute = ({ component: Component, auth, ...rest }) => ( <Route {...rest} render={props => ( auth === true) ? <Redirect to="/lk" /> : <Component {...props} /> )} /> ); export default PrivateRoute;
А вот компонент в с Router’ом
import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom'; import React, {Component, Suspense, lazy } from 'react'; import PrivateRoute from './routs/PrivateRoute'; import './css/App.css'; import logo from './img/logo.png'; import PageMain from './pages/Main/PageMain'; const PageRegister = lazy(() => import('./components/Registration/RegistrationContainer')); const PageAuth = lazy(()=>import('./components/Auth/AuthContainer')); const PageLk = lazy(()=> import('./pages/Lk/Page__Lk')); const auth = // некая переменная или функция которая возвращает значение true или false в зависимости авторизирован пользователь или нет class App extends Component{ render(){ return ( <div className="container"> <div className="row"> <Router> <div className="col-12 header"> <div className="row"> <div className="col-md-6"> <Link to="/" className="text-white"> <img title="На главную" className="img-fluid" src={logo} alt="logtype"/> </Link> </div> <div className="col-md-6"> <p className="text-white header__phone">Какой-то текст</p> </div> </div> </div> <Suspense fallback={<div className="preloader">Загрузка...</div>}> <Switch> <Route exact path="/" component={ (()=>(<PageMain />)) } /> <Route path="/register" component={ ()=>(<PageRegister/>) } /> <Route path="/auth" component={ ()=>(<PageAuth/>) } /> <PrivateRoute auth={auth} path="/lk" component={ ()=>(<PageLk/>) } /> </Switch> </Suspense> </Router> </div> </div> ); } } export default App;
Если вы хотите подробнее познакомиться c router то прочитайте статью react-router-dom.
Теперь нам нужно понимать, авторизирован пользователь или нет. Для этого я использую redux. Создал папку store в корне проекта и в нём папку auth. В папке два файла actions.js и redusers.js
В файле actions.js я создал несколько action creater’ов
export const AUTH_CHANGE_PHONE_TEXT = 'AUTH_CHANGE_PHONE_TEXT'; export const AUTH_CHANGE_PASSWORD_TEXT = 'AUTH_CHANGE_PASSWORD_TEXT'; export const AUTH_CHANGE_VALID_PHONE_TEXT = 'AUTH_CHANGE_VALID_PHONE_TEXT'; export const AUTH_CHANGE_VALID_PASSWORD_TEXT = 'AUTH_CHANGE_VALID_PASSWORD_TEXT'; export const AUTH_SET_IS_AUTH_VALUE = 'AUTH_SET_IS_AUTH_VALUE'; export const setPhoneText = (phone) => ({ type: AUTH_CHANGE_PHONE_TEXT, payload: phone }); export const setValidPhone = (bool) => ({ type: AUTH_CHANGE_VALID_PHONE_TEXT, payload: bool }); export const setPasswordText = (password) => ({ type: AUTH_CHANGE_PASSWORD_TEXT, payload: password }); export const setValidPassword = (bool) => ({ type: AUTH_CHANGE_VALID_PASSWORD_TEXT, payload: bool }); // Значение которое будет отвечать за то авторизирован пользователь или нет export const setIsAuthValue = (bool) => ({ type: AUTH_SET_IS_AUTH_VALUE, payload: bool });
В файле redusers.js
import { AUTH_CHANGE_PHONE_TEXT, AUTH_CHANGE_PASSWORD_TEXT, AUTH_CHANGE_VALID_PHONE_TEXT, AUTH_CHANGE_VALID_PASSWORD_TEXT, AUTH_SET_IS_AUTH_VALUE } from './actions'; const defaultState = { phone: '', password: '', isValidPhone: false, isValidPassword: false, isAuthValue: false } export const authReduser = ( state = defaultState, action ) => { switch( action.type ){ case AUTH_CHANGE_PHONE_TEXT: return { ...state, phone: action.payload } case AUTH_CHANGE_PASSWORD_TEXT: return { ...state, password: action.payload } case AUTH_CHANGE_VALID_PHONE_TEXT: return { ...state, isValidPhone: action.payload } case AUTH_CHANGE_VALID_PASSWORD_TEXT: return { ...state, isValidPassword: action.payload } case AUTH_SET_IS_AUTH_VALUE: return { ...state, isAuthValue: action.payload } default: return state; } }
В папке store так же создаём файл redusers.js и actions.js. В actions.js ничего не пишем, а в файле redusers.js:
import { combineReducers } from "redux"; import { authReduser } from './auth/redusers'; // Прочие импорты export default combineReducers({ auth: authReduser, // Прочие reduser });
Если вам сложно разобраться с redux, рекомендую посмотреть курс на youtube от codedojo.
Переходим в компонент с роутером и модифицируем его
// Импортируем redux и наши redusers import { createStore } from 'redux'; import { Provider } from 'react-redux'; import rootRedusers from './store/redusers'
Создаём store
const store = createStore(rootRedusers);
Оборачиваем весь наш компонент в <Provider>
return( <Provider store={store}> // Остальной код компонента </Provider> )
Изменяем
<PrivateRoute auth={auth} path="/lk" component={ ()=>(<PageLk/>) } />
На
<PrivateRoute store={store} path="/lk" component={ ()=>(<PageLk/>) } />
Идём опять в наш файл с PrivateRoute и переписываем его
import React from 'react'; import { Route, Redirect } from 'react-router-dom'; const PrivateRoute = ({ component: Component, store, ...rest }) => ( <Route {...rest} render={props => ( 'auth' in store.getState() && store.getState().auth.isAuthValue === true) ? <Component {...props} /> : <Redirect to="/auth" /> )} /> ); export default PrivateRoute;
Тут через store.getState() мы проверяем есть ли на данный момент ‘auth’ в storе. В начале жизни нашего приложения его может не быть. И ещё мы проверяем наше isAuthValue на true
У меня есть AuthContainer, который лежит по адресу: /components/Auth/AuthContainer’. И там же лежит компонент Auth, в котором и происходит сама регистрация, лежит вёрстка и т.д.
AuthContainer — умный компонент, который и передаёт данные в Auth. У меня получился вот такой код компонента:
import React, { Component } from 'react'; import Auth from './Auth'; // Нужно, чтобы передать наш store и методы в AuthContainer import { connect } from 'react-redux'; // Импортирую экшены, которые буду использовать в своём приложении import { setPhoneText, setPasswordText, setValidPassword, setValidPhone, setIsAuthValue } from '../../store/auth/actions'; class AuthContainer extends Component { render() { // Передаю props'ы в Auth return <Auth phone={this.props.phone} password={this.props.password} isValidPhone={this.props.isValidPhone} isValidPassword={this.props.isValidPassword} isAuthValue={this.props.isAuthValue} setPhoneText={this.props.setPhoneText} setPasswordText={this.props.setPasswordText} setValidPhone={this.props.setValidPhone} setValidPassword={this.props.setValidPassword} setIsAuthValue={this.props.setIsAuthValue} /> } } // Перечисляю, что из состояний store будет передано компоненту AuthContaine и далее Auth const mapStateToProps = state => { return { phone: state.auth.phone, password: state.auth.password, isValidPhone: state.auth.isValidPhone, isValidPassword: state.auth.isValidPassword, isAuthValue: state.auth.isAuthValue }; }; // Перечисляю методы которые будут переданы в AuthConatainer, а потом в Auth const mapDispatchToProps = { setPhoneText, setPasswordText, setValidPassword, setValidPhone, setIsAuthValue } // Через функцию connect передаю всё в компонент AuthContainer export default connect(mapStateToProps, mapDispatchToProps)(AuthContainer);
Обратите внимание, что в итоге компонент AuthConatainer будет использоваться внутри <Provider>, иначе ничего не сработает.
После того как мы передали в Auth reduser’ы из AuthContainer, у нас появилась функция this.props.setIsAuthValue(bool).
В тот момент, когда мы убедились, что пользователь авторизировался, мы можем вызвать this.props.setIsAuthValue(true). Таким образом значение isAuthValue измениться на true. Кстати, если вы делаете кросс-доменные запросы для авторизации, как я, то вам может быть полезной статья «fetch и cors, пример на reactjs«
Для лучшего UX после авторизации я перенаправлял пользователей в личный кабинет. Для этого мне понадобилось изменить state компонента, чтобы вызвать повторный reder, проверить значение isAuthValue. Примерно это может быть вот так:
render( if(this.props.isAuthValue === false){ return(<div><!-- форма регистрации --></div>) } else { return(<Redirect to='ДРУГАЯ СТРАНИЦА'/>) } )
На самом деле, если разобраться, это всё довольно не сложно. При частой практике вы будете очень быстро справляться с react-redux и react-dom-router будет помогать делать шустрые SPA и не станет усложнением. Однажды я расскажу как настроить виртуальный сервер windows и apache так, чтобы работал router как нужно.
Праведный «срач» поддерживается, а также вопросы и собственный опыт.