Как справиться с перегрузкой функций машинописного текста с помощью React Props

Я новичок в Typescript, и я пытаюсь создать компоненты React с разными вариантами, IE a Button с вариантом 'foo' и вариантом 'bar'.

Я хотел бы использовать компонент <Button variant="foo" />, для которого я бы использовал такой интерфейс:

interface Props {
  variant: 'foo' | 'bar';
  prop1?: boolean;
  prop2?: boolean;
}

При создании кнопки с опорой variant="foo" я хочу разрешить одни свойства и отключить другие. То же и с другими вариантами. По этой причине я создал такие интерфейсы, которые являются результатом многих поисков в google / stackoverflow:

interface Props {
  variant?: never;
  prop1?: boolean;
  prop2?: boolean;
}

type Variant = 'foo' | 'bar' | 'baz';

interface DefaultVariant extends Omit<Props, 'variant'> {
  variant: Variant;
} 

interface FooVariant extends DefaultVariant {
  variant: 'foo';
  prop1?: never;
}

interface BarVariant extends DefaultVariant {
  variant: 'bar';
  prop1?: never;
  prop2?: never;
}

interface BazVariant extends DefaultVariant {
  variant: 'baz';
  prop1?: never;
  prop3: boolean;
}

... чтобы использовать такие перегрузки функций:

export default function Component(props: Props): ReactElement;
export default function Component(props: BazVariant): ReactElement;
export default function Component(props: BarVariant): ReactElement;
export default function Component(props: FooVariant): ReactElement;
export default function Component(props: Props | BarVariant | BazVariant | FooVariant): ReactElement {
  const ComponentVariant = variants[props.variant || 'foo']
  return (
    <ComponentVariant />
  )
}

const Foo = () => <div>Foo</div>

const Bar = () => <div>Bar</div>

const Baz = () => <div>Baz</div>



type Variants = {[key in Variant]: (props: any) => ReactElement };

const variants: Variants = {
  foo: Foo,
  bar: Bar,
  baz: Baz,
}

В надежде использовать компонент так:

function App(): ReactElement {
  return (
    <>
      {/* should work: */}
      <Component /> 
      <Component prop1 prop2/>
      <Component variant="foo" prop2 />
      <Component variant="bar" />
      <Component variant="baz" prop2 prop3 />

      {/* should fail on prop1: */}
      {/* this works */}
      <Component variant="foo" prop1 prop2 />

      {/* should fail on presence of prop1 and prop2: */}
      {/* but fails on variant and prop1: */}
      {/* No overload matches this call.
        The last overload gave the following error.
        Type '"bar"' is not assignable to type '"foo"'.
        Type 'true' is not assignable to type 'undefined' */}
      <Component variant="bar" prop1 prop2 />

      {/* should fail on presence of prop1 and lack of prop3: */}
      {/* but fails on variant: */}
      <Component variant="baz" prop1 prop2 />
    </>
  )
}

И хотя этот вид работает, в том смысле, что я не могу создать Component без правильных свойств, точка отказа, определенная Typescript, неверна.

Например, <Component variant="bar" prop1 prop2/> throws Этому вызову не соответствует никакая перегрузка. как и ожидалось, но в нем ошибочно указано, что 'bar' is not assignable to type 'foo', где проблема на самом деле заключается в назначении prop1 и prop2, которые не разрешены в BarVariant interface.

Моя проблема, насколько я могу судить, заключается в том, что Typescript всегда, кажется, делает вывод о последней перегрузке, где я ожидал (и хотел бы!), Чтобы вывести перегрузку, связанную с опцией варианта.

Если бы кто-нибудь мог пролить свет на то, что я делаю здесь неправильно, я был бы очень признателен!


person Kevin Kroon    schedule 30.11.2020    source источник
comment
Напоминает мне мой вопрос на stackoverflow.com/questions/64851834/, может ли это быть связано? Перегрузки TS в зависимости от порядка, может ли порядок изменить его?   -  person Jaden Garcia    schedule 30.11.2020
comment
Спасибо за ваш быстрый ответ. Я попытался упорядочить перегрузки от наиболее конкретных до наименее конкретных. Изменение порядка действительно меняет результаты. Однако, похоже, это только решает проблему, а не решает ее. IE, помещающий перегрузку BarVariant в конце, заставляет машинописный текст выводить все для этого интерфейса.   -  person Kevin Kroon    schedule 30.11.2020
comment
В таком случае, я думаю, мне придется отсидеться и позволить прийти кому-нибудь, кто лучше понимает систему типов TS.   -  person Jaden Garcia    schedule 30.11.2020
comment
Нет проблем, спасибо, что пытаетесь мне помочь!   -  person Kevin Kroon    schedule 30.11.2020


Ответы (1)


Я столкнулся с той же проблемой, вот мой рабочий пример:

interface CommonProps {
    children: React.ReactNode;
}

interface TruncatedProps extends CommonProps {
    truncate?: 'on';
    expanded?: boolean;
}

interface NoTruncatedProps extends CommonProps {
    truncate?: 'off';
}

interface TextProps extends CommonProps {
    truncate?: 'on' | 'off';
    expanded?: boolean;
}

function Text(props: NoTruncatedProps): JSX.Element;
function Text(props: TruncatedProps): JSX.Element;

function Text(props: TextProps) {
    const { children, truncate, expanded } = props;
    const className = truncate ? 'u-text-truncate' : '';

    return (
        <div className={className} aria-expanded={!!expanded}>
            {children}
        </div>
    );
}

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

...
const textProps: TextProps= {
        truncate: 'on',
        expanded: true,
        children: 'Text'
    };
<Text {...textProps} /> <- Typescript warning
...

Вы должны явно указать правильный тип для textProps: TruncatedProps или NoTruncatedPops , что неудобно.

person Alexandr Podoprigora    schedule 03.03.2021