Возможно ли добавлять типы реквизитов к реквизитам компонента react на основе наличия или значения другого реквизита?

#reactjs #typescript

#reactjs #typescript

Вопрос:

У меня есть довольно сложный компонент React в приложении на основе Typescript. Существует несколько групп реквизитов, которые должны требоваться только при наличии другого реквизита / true. Есть ли какой-нибудь способ выполнить это в Typescript? Я еще не слишком продвинулся в наборе текста с ним.

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

Это то, с чем я здесь работаю.

 interface CommonProps {
  ...a bunch of props...
}

interface ManagedProps extends CommonProps {
  managed: true;
  ...props for only when managed is present/true...
}

interface ServerSideProps extends CommonProps {
  serverSide: true;
  ...props for only when serverSide is present/true...
}

interface Props = ???
  

Базовым является то, что CommonProps будет применяться к этому компоненту. Если managed есть true , то ManagedProps также должно применяться. Если serverSide есть true , то ServerSideProps также применяется.
У меня пока нет такой ситуации, но было бы неплохо, если бы необязательные группы реквизитов могли переопределять реквизит, определенный в CommonProps .

Ответ №1:

Я добился большого успеха, используя помощник ts-toolbelt Union.Strict от,,ts-toolbelt».

Сначала давайте определим два ваших отдельных набора возможных типов, объединим их в строгом объединении и напишем защиту типа, которая сообщает нашему компоненту React, какой набор реквизитов мы используем. Я расширю пример, который вы использовали в своей операции:

 import { Union } from 'ts-toolbelt';

interface CommonProps {
  commonProp: string
}

interface ManagedProps extends CommonProps {
  managed: true;
  managedBoolean: boolean;
  managedNumber: number;
}

interface ServerSideProps extends CommonProps {
  serverSide: true;
  serverBoolean: boolean;
  serverNumber: number;
}

const isManaged = (props: Props): props is ManagedProps => {
  return props.managed;
}

type Props = Union.Strict<ManagedProps | ServerSideProps>
  

Теперь давайте напишем компонент React, который использует защиту типов, которую мы написали:

 const OurComponent: React.FC<Props> = (props) => {
  if (isManaged(props)) {
    return <div>Managed {props.commonProp}</div>
  } else {
    return <div>ServerSide {props.commonProp}</div>
  }
}
  

Это приведет к ошибкам, если вы используете неправильные типы. Вы можете проверить это в этом StackBlitz, который я создал.

Ответ №2:

Вы могли бы использовать условные типы, чтобы получить что-то вроде этого. Например:

 interface CommonProps {
    foo: string;
}

interface ManagedProps extends CommonProps {
    managed: true;
    managedOnlyProp: boolean;
}

interface ServerSideProps extends CommonProps {
    serverSide: true;
    serverOnlyProp: boolean;
}

type MappedProps<T> =
    T extends { managed: true } ? ManagedProps :
    T extends { serverSide: true } ? ServerSideProps :
    CommonProps;

// Example function to test this:

declare function takeProps<T>(props: T amp; MappedProps<T>): void;

// And:

// This is OK, neither flag is present, so CommonProps are used:
takeProps({ foo: "" })

// This is an error. The managed flag is present, so we must also include the managed only props:
takeProps({ foo: "", managed: true }) // Gives error: Property 'managedOnlyProp' is missing in type '{ foo: string; managed: true; }' but required in type 'ManagedProps'.

// But this is okay, the flag is present but set to false...
takeProps({ foo: "", managed: false })

// And likewise for the serverSide versions:
takeProps({ foo: "", serverSide: true }) // Gives error: Property 'serverOnlyProp' is missing in type '{ foo: string; serverSide: true; }' but required in type 'ServerSideProps'.
takeProps({ foo: "", serverSide: true, serverOnlyProp: false }) // OK, no errors
takeProps({ foo: "", serverSide: false }) // OK, no errors
  

Вот <a rel="noreferrer noopener nofollow" href="https://www.typescriptlang.org/play/#src=interface CommonProps {
foo: string;
}

interface ManagedProps extends CommonProps {
managed: true;
managedOnlyProp: boolean;
}

interface ServerSideProps extends CommonProps {
serverSide: true;
serverOnlyProp: boolean;
}

type MappedProps =
T extends { managed: true } ? ManagedProps :
T extends { serverSide: true } ? ServerSideProps :
CommonProps;

// Example function to test this:

declare function takeProps(props: T & MappedProps): void;

// And:

// This is OK, neither flag is present, so CommonProps are used:
takeProps({ foo: «» })

// This is an error. The managed flag is present, so we must also include the managed only props:
takeProps({ foo: «», managed: true }) // Gives error: Property ‘managedOnlyProp’ is missing in type ‘{ foo: string; managed: true; }’ but required in type ‘ManagedProps’.

// But this is okay, the flag is present but set to false…
takeProps({ foo: «», managed: false })

// And likewise for the serverSide versions:
takeProps({ foo: «», serverSide: true }) // Gives error: Property ‘serverOnlyProp’ is missing in type ‘{ foo: string; serverSide: true; }’ but required in type ‘ServerSideProps’.
takeProps({ foo: «», serverSide: true, serverOnlyProp: false }) // OK, no errors
takeProps({ foo: «», serverSide: false }) // OK, no errors
» rel=»nofollow noreferrer»>ссылка на игровую площадку.

Обратите внимание, что есть предостережение в том, что это работает только для каждого флага в отдельности. При использовании в комбинации вы все равно можете передать недопустимый объект:

 // This is wrong (missing serverOnlyProp) but no error is produced... :(
takeProps({foo: "", serverSide: true, managed: true, managedOnlyProp: false})