javascript - Typescript (React) funktionsdeklaration korrekt men parametrar visas som "any" typ


Jag byggde en Select-komponent i React som är fullt typad och jag har nu lagt till en multiple-prop till den, som kommer att ändra typen på värdet och onChange-callbacken till att vara av typen Array<T>. Vad multiple-propen gör är att den använder det jag tror kallas en distributiv villkorlig typ för att bestämma typen på både value och onChange i komponentens props på detta sätt:

interface SelectBaseProps<T extends SelectValue = string> {
  options: SelectOption<T>[];
  placeholder?: string;
  disabled?: boolean;
  className?: string;
  searchable?: boolean;
}

export type SelectProps<T extends SelectValue, TMultiple extends boolean = false> = SelectBaseProps<T> &
  (TMultiple extends false
    ? {
        multiple?: TMultiple;
        value: T;
        onChange: (value: T) => void;
      }
    : {
        multiple?: TMultiple;
        value: T[];
        onChange: (value: T[]) => void;
      });

Där SelectValue bara begränsar värdena till att vara av typen sträng eller nummer för tillfället. Jag vet, inte den snyggaste implementeringen men detta är redan efter några iterationer av felsökning. Och sedan är själva select-komponenten i princip bara

export default function Select<T extends SelectValue, TMultiple extends boolean = false>(...) {...}

Nu, vid första anblick verkar detta fungera helt bra! Om jag använder detta i en testkomponent på detta sätt:

function SelectTest() {
  const [val, setVal] = useState<string>();
  const options: SelectOption<string>[] = [
    { label: 'Option 1', value: '1' },
    { label: 'Option 2', value: '2' },
    { label: 'Option 3', value: '3' },
  ];

  return <>
      <Select value={val} options={options} onChange={(x) => console.log(x)} />
      {/* However, this works! */}
      <Select value={val} options={options} onChange={setVal} />
    </>;
}

och håll muspekaren över onChange-propen, det står tydligt att propen för onChange-callbacken är av typen string. Om jag ändrar koden och gör value till en array, är onChange-värdet också av typen array. Men av någon anledning härleds inte typen i funktionen som skickas in i callbacken och TypeScript klagar att Parameter 'x' implicit har typen 'any'.

Så min fråga: Varför kan inte TypeScript härleda typen, även om funktionen är korrekt typad och kan härleda typen även för egna strängtyper?

Det kan vara relaterat till min tsconfig-konfiguration, så jag lade till det i reproduktionsstackblitzen:

https://stackblitz.com/edit/react-ts-wuj3yu?file=Select.tsx,tsconfig.json

Svar #1

Ta en titt: https://stackblitz.com/edit/react-ts-agwgkq?file=Select.tsx,App.tsx

Jag måste vägra att definiera så många typer/egenskaper:

import React, { useState } from 'react';

export type SelectValue = string | number | Array<string> | Array<number>;

type Unwrap<T> = T extends Array<infer R> ? R : T;

export interface SelectOption<T extends SelectValue> {
  label: string;
  value: Unwrap<T>;
}

interface SelectProps<T extends SelectValue> {
  options: SelectOption<T>[];
  value: T;
  onChange: (value: T) => void;
  placeholder?: string;
  disabled?: boolean;
  className?: string;
  searchable?: boolean;
}

export function Select<T extends SelectValue>(props: SelectProps<T>) {
  return <div>Dummy Select</div>;
}

...

import * as React from 'react';
import { Select } from './Select';
import './style.css';

export default function App() {
  const [val, setVal] = React.useState<string>('');
  const options = [
    { label: 'Option 1', value: '1' },
    { label: 'Option 2', value: '2' },
    { label: 'Option 3', value: '3' },
  ];
  const [multipleVal, setMultipleVal] = React.useState<Array<string>>([]);

  return (
    <React.Fragment>
      <Select value={val} options={options} onChange={(x) => console.log(x)} /> // x is string
      {/* However, this works! */}
      <Select
        value={multipleVal}
        options={options}
        onChange={(x) => console.log(x)} // x is Array<string>
      />
    </React.Fragment>
  );
}