React TypeScript HoC - передача Component в качестве опоры

Следуя этому руководству: https://reacttraining.com/react-router/web/example/auth-workflow.

Пытаемся воспроизвести код:

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

В TypeScript:

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

interface Props extends RouterProps {
  component: React.Component;
}

const PrivateRoute = ({ component: Component, ...rest }: Props) => {
  return (
    <Route
      {...rest}
      render = {(props) => <Component {...props} />}
    />
  );
};

export default PrivateRoute;

Но он всегда терпел неудачу. Пробовал разные варианты. Тот, который я опубликовал в последний раз. Получающий:

React TypeScript HoC - передача Component в качестве опоры

Мне кажется, что мне нужно передать Generic для типа Component, но я не знаю как.

Обновлено:

Ближайшее решение на данный момент:

interface Props extends RouteProps {
  component: () => any;
}

const PrivateRoute = ({ component: Component, ...rest }: Props) => {
  return (
    <Route
      {...rest}
      render = {(props) => <Component {...props} />}
    />
  );
};

А потом:

<PrivateRoute component = {Foo} path = "/foo" />
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
24
0
17 298
2

Ответы 2

Вы хотите передать конструктор компонента, а не экземпляр компонента:

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

interface Props extends RouteProps {
    component: React.ComponentType;
}

const PrivateRoute = ({ component: Component, ...rest }: Props) => {
    return (
        <Route
            {...rest}
            render = {(props) => <Component {...props} />}
        />
    );
};

export default PrivateRoute;

class Foo extends React.Component {

}
let r = <PrivateRoute component = {Foo} path = "/foo" />

Редактировать

Более полное решение должно быть общим и использовать RouteProps вместо RouterProps:

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

type Props<P> =  RouteProps & P & {
    component: React.ComponentType<P>;
}

const PrivateRoute = function <P>(p: Props<P>) {
    // We can't use destructuring syntax, because : "Rest types may only be created from object types", so we do it manually.
    let rest = omit(p, "component");
    let Component = p.component;
    return (
        <Route
            {...rest}
            render = {(props: P) => <p.component {...props} />}
        />
    );
};

// Helpers
type Diff<T extends string, U extends string> = ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T];  
type Omit<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>; 
function omit<T, TKey extends keyof T>(value:T, ... toRemove: TKey[]): Omit<T, TKey>{
    var result = Object.assign({}, value);
    for(let key of toRemove){
        delete result[key];
    }
    return result;
}


export default PrivateRoute;

class Foo extends React.Component<{ prop: number }>{

}
let r = <PrivateRoute component = {Foo} path = "/foo" prop = {10} />

Это похоже на движение в правильном направлении. Однако не знаю, как использовать PrivateRoute в этом случае. Что-то вроде <PrivateRoute component = {Foo} path = "/foo" /> выдает ошибку несоответствия свойств. Таким образом, либо один использует что-то отличное от React.Component, либо предоставляет другой props при использовании PrivateRoute.

0leg 14.03.2018 10:54

@Oleg обновил решение, сделав его универсальным, и исправил использование RouterProps до RouteProps.

Titian Cernicova-Dragomir 14.03.2018 11:08

Спасибо, что уделили время этому и развернутому ответу. Однако это кажется излишним. Я обновил описание, указав наиболее близкий к цели код. Теперь стоит задача найти правильную подпись для component. Согласно github.com/ReactTraining/react-router/blob/master/packages/… - это просто функция. Отладчик указал его как () => Element, но это не работает.

0leg 14.03.2018 11:30

@Oleg Если универсальная версия избыточна (я не думаю, что это обязательно, потому что вы будете использовать ее повторно, но это ваш выбор), почему первое решение не работает? Я включил для него полный код, он компилируется для меня. У вас есть проблемы с этим?

Titian Cernicova-Dragomir 14.03.2018 11:34

После нескольких часов и некоторого исследования вот решение, которое соответствует моим требованиям:

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

const PrivateRoute: React.SFC<RouteProps> =
  ({ component: Component, ...rest }) => {
    if (!Component) {
      return null;
    }
    return (
      <Route
        {...rest}
        render = {(props: RouteComponentProps<{}>) => <Component {...props} />}
      />
    );
  };

export default PrivateRoute;

  • Нет any;
  • Никаких лишних сложностей;
  • Шаблон композиции сохранен;

Я думаю, вы имели в виду export default PrivateRoute; в своей последней строке

Titian Cernicova-Dragomir 14.03.2018 13:35

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