Как да се справим с претоварването на функцията на typescript с React Props

Аз съм начинаещ в Typescript и се опитвам да създам компоненти на React с различни варианти, т.е. бутон с вариант „foo“ и „bar“.

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

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

Когато създавам бутон с реквизит variant="foo", искам да разреша определени реквизити и да деактивирам други. Същото и с другите варианти. Поради тази причина създадох интерфейси като so, което е резултат от много търсения в 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 />
    </>
  )
}

И докато този вид работи, в смисъл, че не мога да създам компонент без правилните подпори, точката на повреда, определена от Typescript, не е правилна.

Например <Component variant="bar" prop1 prop2/> хвърля Никакво претоварване не съответства на това извикване. както се очаква, но погрешно се посочва, че '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 в края, кара typescript да изведе всичко към този интерфейс.   -  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