Я пытался реализовать аутентифицированные маршруты, но обнаружил, что React Router 4 теперь не позволяет это сделать:
<Route exact path="/" component={Index} />
<Route path="/auth" component={UnauthenticatedWrapper}>
<Route path="/auth/login" component={LoginBotBot} />
</Route>
<Route path="/domains" component={AuthenticatedWrapper}>
<Route exact path="/domains" component={DomainsIndex} />
</Route>
Ошибка:
Предупреждение: Вы не должны использовать
<Route component>
и<Route children>
в одном маршруте;<Route children>
будут проигнорированы.
В таком случае, как правильно это реализовать?
В документации по react-router
(v4) предлагается что-то вроде
<Router>
<div>
<AuthButton/>
<ul>
<li><Link to="/public">Public Page</Link></li>
<li><Link to="/protected">Protected Page</Link></li>
</ul>
<Route path="/public" component={Public}/>
<Route path="/login" component={Login}/>
<PrivateRoute path="/protected" component={Protected}/>
</div>
</Router>
Но возможно ли добиться этого, группируя кучу маршрутов вместе?
UPDATE
Так, после некоторых исследований я пришел к следующему:
import React, {PropTypes} from "react"
import {Route} from "react-router-dom"
export default class AuthenticatedRoute extends React.Component {
render() {
if (!this.props.isLoggedIn) {
this.props.redirectToLogin()
return null
}
return <Route {...this.props} />
}
}
AuthenticatedRoute.propTypes = {
isLoggedIn: PropTypes.bool.isRequired,
component: PropTypes.element,
redirectToLogin: PropTypes.func.isRequired
}
Правильно ли отправлять действие в render()
, это кажется неправильным. Это не кажется правильным с componentDidMount
или каким-то другим хуком?
Вы захотите использовать компонент Redirect
. Существует несколько различных подходов к решению этой проблемы. Вот один из них, который мне нравится: есть компонент PrivateRoute, который принимает реквизит authed
и затем рендерит на основе этого реквизита.
function PrivateRoute ({component: Component, authed, ...rest}) {
return (
<Route
{...rest}
render={(props) => authed === true
? <Component {...props} />
: <Redirect to={{pathname: '/login', state: {from: props.location}}} />}
/>
)
}
Теперь ваш маршрут
может выглядеть примерно так
<Route path='/' exact component={Home} />
<Route path='/login' component={Login} />
<Route path='/register' component={Register} />
<PrivateRoute authed={this.state.authed} path='/dashboard' component={Dashboard} />
Если вы все еще запутались, я написал этот пост, который может помочь - Защищенные маршруты и аутентификация в React Router v4
Спасибо Тайлеру МакГиннису за решение. Я создал свою идею на основе идеи Тайлера МакГинниса.
const DecisionRoute = ({ trueComponent, falseComponent, decisionFunc, ...rest }) => {
return (
<Route
{...rest}
render={
decisionFunc()
? trueComponent
: falseComponent
}
/>
)
}
Вы можете реализовать это следующим образом
<DecisionRoute path="/signin" exact={true}
trueComponent={redirectStart}
falseComponent={SignInPage}
decisionFunc={isAuth}
/>
decisionFunc просто функция, которая возвращает true или false
const redirectStart = props => <Redirect to="/orders" />
Просто добавив свое решение проблемы.
Я использую токенов JWT для аутентификации, так что если пользователь имеет этот знак тогда я буду перенаправлять их на главную страницу, иначе я буду перенаправлять их на страницу входа по умолчанию (который этот маршрут '/'). Поэтому, как только пользователь вошел в систему, и попробовать получить доступ к URL-адрес входа страницу (в моем случае '/') . Я буду перенаправлять их к дому по умолчанию('/домой').
И мои компоненты имеют специального имени requireAuth, чтобы проверить, если маркер пользователя является действительным.если нет, то позвоните в файле signout действие, которое удаляет localhistory знак.
import React, { Component, Fragment } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route, Switch, Redirect } from 'react-router-dom';
//and also import appropriate components
//middleware
class checkStatus extends React.Component {
render() {
if(localStorage.getItem('token')){
return (
<Fragment>
<App>
<Route path="/home" exact component={Overview} />
<Route path="/home/add" exact component={Add} />
<Route path="/signout" component={Signout} />
<Route path="/details" component={details} />
<Route exact path="/" render={() => <Redirect to="/home" />} />
</App>
</Fragment>
)
}else{
return (
<Fragment>
<Route path="/" exact component={Signin} />
<Redirect to="/" />
</Fragment>
)
}
} }
ReactDOM.render( <Provider store={store}>
<BrowserRouter>
<Switch >
<Route path="/" exact component={checkStatus} />
<Route path="/:someParam" component={checkStatus}/>
</Switch >
</BrowserRouter> </Provider>, document.querySelector('#root')
);
установите реагировать-маршрутизатор-дом
затем создайте два компонента, один для действительных пользователей и других недопустимых пользователей.
попробуй это app.js
import React from 'react';
import {
BrowserRouter as Router,
Route,
Link,
Switch,
Redirect
} from 'react-router-dom';
import ValidUser from "./pages/validUser/validUser";
import InValidUser from "./pages/invalidUser/invalidUser";
const loggedin = false;
class App extends React.Component {
render() {
return (
<Router>
<div>
<Route exact path="/" render={() =>(
loggedin ? ( <Route component={ValidUser} />)
: (<Route component={InValidUser} />)
)} />
</div>
</Router>
)
}
}
export default App;
На основании ответа @Тайлер Макгиннис. Я сделал другой подход, используя на ES6 синтаксис и вложенные маршруты с обернутой компоненты:
import React, { cloneElement, Children } from 'react'
import { Route, Redirect } from 'react-router-dom'
const PrivateRoute = ({ children, authed, ...rest }) =>
<Route
{...rest}
render={(props) => authed ?
<div>
{Children.map(children, child => cloneElement(child, { ...child.props }))}
</div>
:
<Redirect to={{ pathname: '/', state: { from: props.location } }} />}
/>
export default PrivateRoute
И использовать его:
<BrowserRouter>
<div>
<PrivateRoute path='/home' authed={auth}>
<Navigation>
<Route component={Home} path="/home" />
</Navigation>
</PrivateRoute>
<Route exact path='/' component={PublicHomePage} />
</div>
</BrowserRouter>
Я знаю, что это'ы было долгое время, но я'вэ работает на НПМ пакет для частных и государственных маршрутов.
Здесь's, Как сделать частный маршруту:
<PrivateRoute exact path="/private" authed={true} redirectTo="/login" component={Title} text="This is a private route"/>
И вы также можете сделать общественные маршруты, которые только unauthed пользователь может получить доступ
<PublicRoute exact path="/public" authed={false} redirectTo="/admin" component={Title} text="This route is for unauthed users"/>
Я надеюсь, что это помогает!
Мой предыдущий ответ не является масштабируемым. Вот что я думаю, это хороший подход-
Ваши Маршруты-
<Switch>
<Route
exact path="/"
component={matchStateToProps(InitialAppState, {
routeOpen: true // no auth is needed to access this route
})} />
<Route
exact path="/profile"
component={matchStateToProps(Profile, {
routeOpen: false // can set it false or just omit this key
})} />
<Route
exact path="/login"
component={matchStateToProps(Login, {
routeOpen: true
})} />
<Route
exact path="/forgot-password"
component={matchStateToProps(ForgotPassword, {
routeOpen: true
})} />
<Route
exact path="/dashboard"
component={matchStateToProps(DashBoard)} />
</Switch>
Идея в том, чтобы использовать оболочку в компонент
реквизит, который будет возвращать исходный компонент, если нет авторизации требуется или уже прошедших проверку подлинности, в противном случае вернется компонент по умолчанию логины.
const matchStateToProps = function(Component, defaultProps) {
return (props) => {
let authRequired = true;
if (defaultProps && defaultProps.routeOpen) {
authRequired = false;
}
if (authRequired) {
// check if loginState key exists in localStorage (Your auth logic goes here)
if (window.localStorage.getItem(STORAGE_KEYS.LOGIN_STATE)) {
return <Component { ...defaultProps } />; // authenticated, good to go
} else {
return <InitialAppState { ...defaultProps } />; // not authenticated
}
}
return <Component { ...defaultProps } />; // no auth is required
};
};
Я реализовал с помощью-
<Route path='/dashboard' render={() => (
this.state.user.isLoggedIn ?
(<Dashboard authenticate={this.authenticate} user={this.state.user} />) :
(<Redirect to="/login" />)
)} />
подлинность реквизит будет передан компонентов, например, регистрация с помощью которых государство пользователя могут быть изменены. Полное AppRoutes-
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import { Redirect } from 'react-router';
import Home from '../pages/home';
import Login from '../pages/login';
import Signup from '../pages/signup';
import Dashboard from '../pages/dashboard';
import { config } from '../utils/Config';
export default class AppRoutes extends React.Component {
constructor(props) {
super(props);
// initially assuming that user is logged out
let user = {
isLoggedIn: false
}
// if user is logged in, his details can be found from local storage
try {
let userJsonString = localStorage.getItem(config.localStorageKey);
if (userJsonString) {
user = JSON.parse(userJsonString);
}
} catch (exception) {
}
// updating the state
this.state = {
user: user
};
this.authenticate = this.authenticate.bind(this);
}
// this function is called on login/logout
authenticate(user) {
this.setState({
user: user
});
// updating user's details
localStorage.setItem(config.localStorageKey, JSON.stringify(user));
}
render() {
return (
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/login' render={() => <Login authenticate={this.authenticate} />} />
<Route exact path='/signup' render={() => <Signup authenticate={this.authenticate} />} />
<Route path='/dashboard' render={() => (
this.state.user.isLoggedIn ?
(<Dashboard authenticate={this.authenticate} user={this.state.user} />) :
(<Redirect to="/login" />)
)} />
</Switch>
);
}
}
Проверьте здесь полный проект: https://github.com/varunon9/hello-react
Похоже, вы сомневаетесь в создании собственного компонента и последующей диспетчеризации в методе render? Вы можете избежать и того, и другого, просто используя метод render
компонента <Route>
. Нет необходимости создавать компонент <AuthenticatedRoute>
, если вы действительно этого не хотите. Он может быть таким же простым, как показано ниже. Обратите внимание на распространение {...routeProps}
, чтобы убедиться, что вы продолжаете передавать свойства компонента <Route>
дочернему компоненту (<MyComponent>
в данном случае).
<Route path='/someprivatepath' render={routeProps => {
if (!this.props.isLoggedIn) {
this.props.redirectToLogin()
return null
}
return <MyComponent {...routeProps} anotherProp={somevalue} />
} />
См. документацию React Router V4 render documentation
Если вы действительно хотели создать специальный компонент, то, похоже, вы на правильном пути. Поскольку React Router V4 - это чисто декларативная маршрутизация (об этом говорится прямо в описании), я не думаю, что вам удастся вынести код перенаправления за пределы обычного жизненного цикла компонента. Если посмотреть на код самого React Router, то они выполняют перенаправление либо в componentWillMount
, либо в componentDidMount
, в зависимости от того, выполняется ли рендеринг на стороне сервера или нет. Ниже приведен код, который довольно прост и может помочь вам почувствовать себя более уверенно в том, где разместить логику перенаправления.
import React, { PropTypes } from 'react'
/**
* The public API for updating the location programatically
* with a component.
*/
class Redirect extends React.Component {
static propTypes = {
push: PropTypes.bool,
from: PropTypes.string,
to: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object
])
}
static defaultProps = {
push: false
}
static contextTypes = {
router: PropTypes.shape({
history: PropTypes.shape({
push: PropTypes.func.isRequired,
replace: PropTypes.func.isRequired
}).isRequired,
staticContext: PropTypes.object
}).isRequired
}
isStatic() {
return this.context.router && this.context.router.staticContext
}
componentWillMount() {
if (this.isStatic())
this.perform()
}
componentDidMount() {
if (!this.isStatic())
this.perform()
}
perform() {
const { history } = this.context.router
const { push, to } = this.props
if (push) {
history.push(to)
} else {
history.replace(to)
}
}
render() {
return null
}
}
export default Redirect
Вот как я решил его с реагировать и машинопись. Надеюсь, что это помогает !
в
import * as React from 'react';
import { Route, RouteComponentProps, RouteProps, Redirect } from 'react-router';
const PrivateRoute: React.SFC<RouteProps> = ({ component: Component, ...rest }) => {
if (!Component) {
return null;
}
const isLoggedIn = true; // Add your provider here
return (
<Route
{...rest}
render={(props: RouteComponentProps<{}>) => isLoggedIn ? (<Component {...props} />) : (<Redirect to={{ pathname: '/', state: { from: props.location } }} />)}
/>
);
};
export default PrivateRoute;
<PrivateRoute component={SignIn} path="/signin" />
в
const Root = ({ session }) => {
const isLoggedIn = session && session.getCurrentUser
return (
<Router>
{!isLoggedIn ? (
<Switch>
<Route path="/signin" component={<Signin />} />
<Redirect to="/signin" />
</Switch>
) : (
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" component={About} />
<Route path="/something-else" component={SomethingElse} />
<Redirect to="/" />
</Switch>
)}
</Router>
)
}
Вот простой чистой заповедной маршрут
const ProtectedRoute
= ({ isAllowed, ...props }) =>
isAllowed
? <Route {...props}/>
: <Redirect to="/authentificate"/>;
const _App = ({ lastTab, isTokenVerified })=>
<Switch>
<Route exact path="/authentificate" component={Login}/>
<ProtectedRoute
isAllowed={isTokenVerified}
exact
path="/secrets"
component={Secrets}/>
<ProtectedRoute
isAllowed={isTokenVerified}
exact
path="/polices"
component={Polices}/>
<ProtectedRoute
isAllowed={isTokenVerified}
exact
path="/grants" component={Grants}/>
<Redirect from="/" to={lastTab}/>
</Switch>
isTokenVerified
- это вызов метода для проверки авторизации токен в основном она возвращает логическое значение.
Я тоже искал какой-то ответ. Здесь все ответы неплохи, но никто из них не даст ответы, как мы можем использовать его, если пользователь запускает приложение после открытия его обратно. (Я хотел сказать, используя куки).
Нет необходимости создавать даже разных компонент privateRoute. Ниже мой код
import React, { Component } from 'react';
import { Route, Switch, BrowserRouter, Redirect } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from './stores';
import requireAuth from './components/authentication/authComponent'
import SearchComponent from './components/search/searchComponent'
import LoginComponent from './components/login/loginComponent'
import ExampleContainer from './containers/ExampleContainer'
class App extends Component {
state = {
auth: true
}
componentDidMount() {
if ( ! Cookies.get('auth')) {
this.setState({auth:false });
}
}
render() {
return (
<Provider store={store}>
<BrowserRouter>
<Switch>
<Route exact path="/searchComponent" component={requireAuth(SearchComponent)} />
<Route exact path="/login" component={LoginComponent} />
<Route exact path="/" component={requireAuth(ExampleContainer)} />
{!this.state.auth && <Redirect push to="/login"/> }
</Switch>
</BrowserRouter>
</Provider>);
}
}
}
export default App;
И вот authComponent
import React from 'react';
import { withRouter } from 'react-router';
import * as Cookie from "js-cookie";
export default function requireAuth(Component) {
class AuthenticatedComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
auth: Cookie.get('auth')
}
}
componentDidMount() {
this.checkAuth();
}
checkAuth() {
const location = this.props.location;
const redirect = location.pathname + location.search;
if ( ! Cookie.get('auth')) {
this.props.history.push(`/login?redirect=${redirect}`);
}
}
render() {
return Cookie.get('auth')
? <Component { ...this.props } />
: null;
}
}
return withRouter(AuthenticatedComponent)
}
Ниже я написал блог, вы можете получить более подробное описание там же.