Реализовать React-router PrivateRoute в проекте Typescript

Вот пример от response-router о том, как добавить компонент для защищенных маршрутов:

function PrivateRoute({ component: Component, ...rest }) {
  return (
    <Route
      {...rest}
      render = {props =>
        fakeAuth.isAuthenticated ? (
          <Component {...props} />
        ) : (
          <Redirect
            to = {{
              pathname: "/login",
              state: { from: props.location }
            }}
          />
        )
      }
    />
  );
}

https://reacttraining.com/react-router/web/example/auth-workflow

Я попытался реализовать эту функциональность в моем проекте Typescript, взяв за основу приведенный выше пример.

компоненты / маршруты

import PrivateRoute from '../../connectors/PrivateRoute';
<PrivateRoute path = "/codes" component = {SomePage} />

разъемы / PrivateRoute

import { connect } from 'react-redux';
import { AppState } from 'app-types';
import PrivateRouteComponent from '../../components/PrivateRoute';

const mapStateToProps = (state: AppState) => {
    const isSignedIn = state.user.isSignedIn;

    return {
        isSignedIn
    };
};

const PrivateRoute = connect(
    mapStateToProps,
    null
)(PrivateRouteComponent);

export default PrivateRoute;

компоненты / PrivateRoute

import * as React from 'react';
import {
    Route,
    Redirect,
} from 'react-router-dom';

interface PrivateRouteProps {
    // tslint:disable-next-line:no-any
    component: any;
    isSignedIn: boolean;
    // tslint:disable-next-line:no-any
    location: any;
}

const PrivateRoute = (props: PrivateRouteProps) => {
    const { component: Component, isSignedIn, location, ...rest } = props;

    return (
        <Route
            {...rest}
            render = {(routeProps) =>
                isSignedIn ? (
                    <Component {...routeProps} />
                ) : (
                        <Redirect
                            to = {{
                                pathname: '/signin',
                                state: { from: location }
                            }}
                        />
                    )
            }
        />
    );
};

export default PrivateRoute;

Ошибка

(105,18): Type '{ path: string; component: ConnectedComponentClass<typeof SomePage, Pick<SomePageProps, never>>; }' is not assignable to type 'Readonly<Pick<PrivateRouteProps, "location" | "component">>'.
  Property 'location' is missing in type '{ path: string; component: ConnectedComponentClass<typeof SomePage, Pick<SomePageProps, never>>; }'.
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
11
0
12 024
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Ошибка возникает из-за того, что PrivateRouteProps имеет обязательное свойство location, которое не предоставляется при использовании PrivateRoute в components/Routes.tsx. Я предполагаю, что это местоположение должно поступать от routeProps, который маршрутизатор автоматически передает функции render маршрута, как это было в исходном примере. Как только это будет исправлено, появится другая ошибка: components/Routes.tsx передает свойство paths, которое не объявлено в PrivateRouteProps. Поскольку PrivateRoute передает любую опору, о которой он не знает, в Route, PrivateRouteProps должен расширять RouteProps от react-router, чтобы PrivateRoute принимал все опоры, принятые Route.

Вот components/PrivateRoute.tsx после обоих исправлений:

import * as React from 'react';
import {
    Route,
    Redirect,
    RouteProps,
} from 'react-router-dom';

interface PrivateRouteProps extends RouteProps {
    // tslint:disable-next-line:no-any
    component: any;
    isSignedIn: boolean;
}

const PrivateRoute = (props: PrivateRouteProps) => {
    const { component: Component, isSignedIn, ...rest } = props;

    return (
        <Route
            {...rest}
            render = {(routeProps) =>
                isSignedIn ? (
                    <Component {...routeProps} />
                ) : (
                        <Redirect
                            to = {{
                                pathname: '/signin',
                                state: { from: routeProps.location }
                            }}
                        />
                    )
            }
        />
    );
};

export default PrivateRoute;

Это просто потрясающе! Если я могу задать дополнительный вопрос: почему мне нужно расширять PrivateRouteProps с помощью RouteProps, когда мне обычно не нужно делать это в других моих компонентах?

user1283776 02.11.2018 08:40

Цель расширения RouteProps в этом случае - принять опору path, чтобы ее можно было передать на Route. Если другие компоненты, о которых вы говорите, обертывают Route и передают реквизиты таким же образом, как это делает PrivateRoute, я ожидаю, что их типы пропов должны будут расширять RouteProps; если эти компоненты не обертывают Route, то RouteProps не имеет значения.

Matt McCutchen 02.11.2018 19:21

Я нашел Мэтт ответ очень полезным, но мне нужно, чтобы он работал как с children, так и с component, поэтому изменил его следующим образом:

import * as React from 'react';
import { Route, Redirect, RouteProps } from 'react-router-dom';
import { fakeAuth } from '../api/Auth';

interface PrivateRouteProps extends RouteProps {
  // tslint:disable-next-line:no-any
  component?: any;
  // tslint:disable-next-line:no-any
  children?: any;
}

const PrivateRoute = (props: PrivateRouteProps) => {
  const { component: Component, children, ...rest } = props;

  return (
    <Route
      {...rest}
      render = {routeProps =>
        fakeAuth.isAuthenticated ? (
          Component ? (
            <Component {...routeProps} />
          ) : (
            children
          )
        ) : (
          <Redirect
            to = {{
              pathname: '/signin',
              state: { from: routeProps.location },
            }}
          />
        )
      }
    />
  );
};

export default PrivateRoute;

Примечание. Это происходит с использованием fakeAuth как оригинального обучающая статья, а не с сокращением isSignedIn пользователя1283776, но вы поняли идею.

Текущие ответы работают, но я хотел бы опубликовать свое решение, так как я думаю, что у него есть несколько преимуществ:

  • Не заменять свойство component типом any.
  • Использование метод рендеринга из библиотеки для поддержки как компонента <Route>, так и дочерних реквизитов - без повторной реализации уже существующей логики / кода фреймворка.
  • Использование Recipe: Static Typing из официальной документации react-redux

Пример:

import * as React from 'react';
import { connect, ConnectedProps } from 'react-redux';
import {
    Redirect,
    Route,
    RouteProps,
} from 'react-router-dom';
import { AppState } from '../store';

const mapState = (state: AppState) => ({
  loggedIn: state.system.loggedIn,
});

const connector = connect(
  mapState,
  { }
);

type PropsFromRedux = ConnectedProps<typeof connector>;

type Props = PropsFromRedux & RouteProps & {

};

const PrivateRoute: React.FC<Props> = props => {
    const { loggedIn, ...rest } = props;

    return ( !loggedIn ? <Redirect to = "/login/" /> :
      <Route {...rest} />
    );
};

export default connector(PrivateRoute);

Я использую тип «React.ReactNode» для моих дочерних компонентов вместо каких-либо.

Привет и добро пожаловать в StackOverflow! Пожалуйста, подумайте об обновлении вашего ответа, чтобы он был более ясным и конкретным в отношении того, что нужно изменить в исходном коде, который был опубликован в ответе. Рассмотрите возможность изменения кода и непосредственной публикации в обновленном и рабочем коде или до и после определенных строк, которые были изменены, вместе со ссылкой на файл, из которого они взяты. Это значительно улучшило бы этот ответ.

LightCC 26.04.2020 00:25

Другие вопросы по теме