TypeScript Rest Props — разрешите любые реквизиты или сообщите компоненту, какими реквизитами они будут

Я создаю приложение React с помощью TypeScript. У меня есть компонент RequiresPermission, который на основе предиката должен отображать тот или иной компонент и пересылать все реквизиты.

type Props = {
  NotPermittedComponent: React.ComponentType;
  PermittedComponent: React.ComponentType;
  isPermitted: boolean;
};

const RequiresPermisson = ({
  NotPermittedComponent,
  PermittedComponent,
  isPermitted,
  ...rest
}: Props) =>
  isPermitted ? (
    <PermittedComponent {...rest} />
  ) : (
    <NotPermittedComponent {...rest} />
  );

export default RequiresPermisson;

Когда я визуализирую компонент, TypeScript кричит о RequiresPermission в:

const PERMITTED_TEXT = 'permitted';
const NOT_PERMITTED_TEXT = 'not-permitted';

type TestPropsProps = {
  text: string;
};

const NotPermittedTestComponent: React.FunctionComponent<TestPropsProps> = ({
  text,
}) => (
  <div>
    <span>{NOT_PERMITTED_TEXT}</span>
    {text}
  </div>
);

const PermittedTestComponent: React.FunctionComponent<TestPropsProps> = ({
  text,
}) => (
  <div>
    <span>{PERMITTED_TEXT}</span>
    {text}
  </div>
);

const createProps = ({
  NotPermittedComponent = NotPermittedTestComponent,
  PermittedComponent = PermittedTestComponent,
  isPermitted = false,
  text = 'foo',
} = {}) => ({
  NotPermittedComponent,
  PermittedComponent,
  isPermitted,
  text,
});

const props = createProps();
render(<RequiresPermission {...props} />);

говоря:

Type '{ NotPermittedComponent: FunctionComponent<TestPropsProps>; PermittedComponent: FunctionComponent<TestPropsProps>; isPermitted: boolean; text: string; }' is not assignable to type '{ NotPermittedComponent: ComponentType<{}>; PermittedComponent: ComponentType<{}>; isPermitted: boolean; }'.
  Types of property 'NotPermittedComponent' are incompatible.
    Type 'FunctionComponent<TestPropsProps>' is not assignable to type 'ComponentType<{}>'.
      Type 'FunctionComponent<TestPropsProps>' is not assignable to type 'FunctionComponent<{}>'.
        Types of parameters 'props' and 'props' are incompatible.
          Type '{ children?: ReactNode; }' is not assignable to type 'PropsWithChildren<TestPropsProps>'.
            Property 'text' is missing in type '{ children?: ReactNode; }' but required in type 'TestPropsProps'.

Я также пробовал Record<string, unknown> как props, но это тоже не работает.

Как вы можете исправить это, чтобы либо передать реквизиты, либо разрешить любые (не типа any) реквизиты, чтобы работал параметр ...rest?

разрешить любой реквизит" - (props: any) => ...? unknown имеет больше смысла как тип возвращаемого значения, чем тип параметра.

jonrsharpe 10.12.2020 15:52

поскольку вы не принимаете никаких ответов, я бы предположил, что чего-то не хватает. Не могли бы вы дать какой-либо отзыв о том, на какую часть вашего вопроса не был дан ответ или почему он не соответствует вашему варианту использования?

ian 17.12.2020 12:42
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
4
2
3 380
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вам нужны универсальные типы из машинописного текста: https://www.typescriptlang.org/docs/handbook/generics.html

Короче говоря, это динамические типы, которые зависят от того, что вводится.

В приведенном ниже коде мы назначаем все реквизиты, которые передаются типу T, и сообщаем typescript, что реквизиты: обязательные реквизиты NotPermittedComponentPermittedComponentisPermitted вместе со всеми rest, которые в данном случае становятся типом T.

type Props = {
  NotPermittedComponent: React.ComponentType;
  PermittedComponent: React.ComponentType;
  isPermitted: boolean;
};

const RequiresPermisson = <T extends Record<string, unknown>>({
  NotPermittedComponent,
  PermittedComponent,
  isPermitted,
  ...rest
}: Props & T) =>
  isPermitted ? (
    <PermittedComponent {...rest} />
  ) : (
    <NotPermittedComponent {...rest} />
  );

export default RequiresPermisson;

Спасибо за вашу помощь. Я попробовал ваш код, он выдает ту же ошибку: Types of property 'NotPermittedComponent' are incompatible. Type 'FunctionComponent<TestProps>' is not assignable to type 'ComponentType<{}>'. Type 'FunctionComponent<TestProps>' is not assignable to type 'FunctionComponent<{}>'. Types of parameters 'props' and 'props' are incompatible. Type '{ children?: ReactNode; }' is not assignable to type 'PropsWithChildren<TestProps>'. Property 'text' is missing in type '{ children?: ReactNode; }' but required in type 'TestProps'.

J. Hesters 13.12.2020 11:57

Боже, это сложная штука. Пытался создать способ передачи любого компонента как PermittedComponent и NotPermittedComponent и требовать передачи всех и только реквизитов компонента RequiresPermisson. Искал по всему Интернету и, похоже, это не способ AFAIK. Проблема в динамических свойствах PermittedComponent и NotPermittedComponent, они ВСЕГДА содержат только {text: string}? В этом случае я был бы очень прост.

Gabriel Petersson 13.12.2020 13:53

Я знаю правильно 😕 Спасибо, что использовали свои выходные, чтобы помочь мне, кстати, очень ценю это 🙏 Я также потратил несколько часов на это и не смог найти способ заставить это работать. Нет, PermittedComponent и NotPermittedComponent могут даже содержать оба разных реквизита (то есть один { myNumber: number }, а другой { myBool: boolean }).

J. Hesters 13.12.2020 14:02
type PropsOf<T> = T extends React.ComponentType<infer Props> ? Props : never Я видел, что это где-то использовалось для получения реквизитов переданных компонентов, и я пробовал такие вещи, как const RequiresPermisson = < P extends Record<"PermittedComponent" | "NotPermittedComponent", unknown> >({, и надеялся, что это заставит меня добавить PropsOf<P["PermittedComponent"]> в качестве требуемых реквизитов. Может заставить его работать динамически:/
Gabriel Petersson 13.12.2020 15:11
Ответ принят как подходящий

Не уверен, что это самое элегантное решение, но я только что объединил все типы реквизита. Это дает вам проверку типов для всех реквизитов. Я должен переслать все реквизиты, потому что я не могу убедиться, что что-то вроде isPermitted не требуется для подкомпонента.

interface Props <A, B>{
  NotPermittedComponent: React.ComponentType<A>;
  PermittedComponent: React.ComponentType<B>;
  isPermitted: boolean;
}

const RequiresPermisson = <A, B>(props: Props<A, B> & A & B): JSX.Element => {
  const {
    NotPermittedComponent,
    PermittedComponent,
    isPermitted,
  } = props
  return isPermitted ? (
    <PermittedComponent {...props} />
  ) : (
    <NotPermittedComponent {...props} />
  )
}

А этот классный! Зачем нужны оба <A, B>, а не только <A>? @ian

Gabriel Petersson 14.12.2020 12:13

A и B — это всего лишь реквизиты PermittedComponent и NotPermittedComponent, так как было предложено, чтобы они были разными, нам нужны для них разные типы. A и B будут выводиться из компонентов, которые вы вставляете, и все они будут необходимы/возможны в RequiersPermission. @Габриэль

ian 14.12.2020 13:40

Я принял ответ, потому что, по крайней мере, он компилируется, но технически есть еще одна проблема. PermittedComponent и NotPermittedComponent теперь получают себя и isPermitted передаются им в качестве реквизита, но это не их реквизит. У вас есть какое-нибудь решение для этого?

J. Hesters 18.12.2020 10:50

Я понимаю, что вы беспокоитесь. Но даже с ...rest из вашего кода вы также дадите PermittedComponent реквизит NotPermittedComponent. А что, если я напишу PermittedComponent с isPermitted в качестве опоры. Я не уверен, что есть способ разделить все это, не изолируя реквизиты во что-то вроде permittedComponentProps или передавая их как renderProps.

ian 18.12.2020 13:03

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