Próbuję napisać składnik reagujący, w którym jego właściwości zmieniają się na podstawie rodzaju właściwości ogólnej.

interface PropsBase<T> {
  value: T;
  children: (value: T) => ReactElement;
}

type ArrayType<T extends any[]> = T extends Array<infer U> ? U : never;

interface ArrayProps<T extends any[]> extends Omit<PropsBase<T>, 'children'> {
  children: (value: ArrayType<T>, index: number, array: T) => ReactElement;
}

type Props<T> = T extends any[] ? ArrayProps<T> : PropsBase<T>;

const MyComp = <T extends any>({ value, children }: Props<T>) => {
  if (Array.isArray(value)) {
    return <>{value.map(children)}</>;
  }
  
  return <div>{children(value)}</div>;
};

Jeśli value jest tablicą, a następnie dziecko, którego oczekuje, jest funkcją, która jest stosowana do każdego elementu w tablicy. W przeciwnym razie oczekuje, że funkcja wymaga wartości.

Z powodów, które muszę ustalić, gdy Props jest warunkowy, jak jest powyżej, nie może określić typu T.

const val = { a: 10 };
const rendered = 
  <MyComp value={val}>
    {value => <span>{value.a}</span>}
  </MyComp>;

// error: Object is of type 'unknown'.

Tak jest nawet jeśli oba wyniki operatora trójskiego są identyczne.

type Props<T> = T extends any[] ? PropsBase<T> : PropsBase<T>;
// same error

Wszelkie inne rekwizyty, w tym tylko wymienione w ArrayProps są prawidłowo wywnioskowane.

interface ArrayProps<T extends any[]> extends Omit<PropsBase<T>, 'children'> {
  children: (value: ArrayType<T>, index: number, array: T) => ReactElement;
  arrayOpt: boolean;
}

const array = [ 1, 2, 3 ];
const renderedArray =
  <MyComp value={array} arrayOpt>
    {value => <span>{value}</span>}
  </MyComp>;

Gdybym musiał zgadywać, jest to błąd w TSC. Tymczasem jest wokół tego?

TS Playground

2
dx_over_dt 21 październik 2020, 01:35

1 odpowiedź

Najlepsza odpowiedź

To bałagan, ale znalazłem obejście.

interface MyComp<T> extends FunctionComponent<Props<T>> {};
function MyComp<T extends any[]>(props: ArrayProps<T>): ReactElement;
function MyComp<T>(props: PropsBase<T>): ReactElement;
function MyComp<T>(props: Props<T>) {
  const {
    value,
    children,
    arrayOpt,
  } = props as PropsBase<T> & { arrayOpt: boolean };

  if (Array.isArray(value)) {
    return <>{value.map(children)}</>;
  } else {
    return <div>{(children as PropsBase<T>['children'])(value)}</div>;
  }
};

TS Playground

1
dx_over_dt 20 październik 2020, 23:39