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

#reactjs #typescript #typescript-typings #react-typescript

#reactjs #машинописный текст #машинопись-типизации #реагировать-машинопись

Вопрос:

Я создаю приложение 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> as props , но это тоже не сработало.

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

Комментарии:

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

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

Ответ №1:

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

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

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

Комментарии:

1. A это потрясающе! Зачем вам нужны оба <A, B> , а не только <A> ? @ian

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

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

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

Ответ №2:

Вам нужны общие типы из typescript: https://www.typescriptlang.org/docs/handbook/generics.html

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

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

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

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

export default RequiresPermisson;
 

Комментарии:

1. Спасибо за вашу помощь. Я попробовал ваш код, он выдает ту же ошибку: 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'.

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

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

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