Pesquisa de site

reactjs - Como inferir o tipo com base no valor em javascript


Eu tenho este hook do React que retorna um filtro baseado nas props de tipo que o usuário forneceu.

interface Props {
    type: 'product' | 'content';
}

function useFilterSelector(props: Props) {
    const { t } = useClientTranslation();

    const queryClient = useQueryClient();
    const [filterSelector] = useState(() => {
        switch (props.type) {
            case 'product':
                return new ProductFilter(t, queryClient);
            case 'content':
                return new ContentFilter(t, queryClient);
        }
    });

    return { filterSelector };
}

Este hook funciona bem, mas o problema é que não consigo obter inferência de tipo estrita. Por exemplo, se o desenvolvedor fornecer "produto" como argumento

  const { filterSelector } = useFilterSelector({ type : "product"})

O tipo inferido deve ser

type = ProductFilter

mas eu consigo

type = ProductFilter | ContentFilter

Eu tentei isso

function useFilterSelector(props: Props) {
    const { t } = useClientTranslation();

    type FilterType = (typeof props)['type'];
    type FilterSelector = FilterType extends 'product'
        ? ProductFilter
        : FilterType extends 'content'
        ? ContentFilter
        : unknown;

    const queryClient = useQueryClient();
    const [filterSelector] = useState<FilterSelector>(() => {
        switch (props.type) {
            case 'product':
                return new ProductFilter(t, queryClient);
            case 'content':
                return new ContentFilter(t, queryClient);
        }
    });

    return { filterSelector };
}

mas o tipo é inferido como desconhecido

como eu conserto isso?

Resposta #1

Você pode fazer uso de tipo de retorno condicional baseado no tipo de props.type

interface Props {
    type: 'product' | 'content';
}

interface ProductFilter {
  a: string;
}
interface ContentFilter {
  b: number;
}

interface FilterSelector<T extends Props> {
  filterSelector: T['type'] extends 'product' ? ProductFilter : ContentFilter
}

function useFilterSelector<T extends Props>(props: T): FilterSelector<T> {
  return { filterSelector: {} as any }
}

const pF = useFilterSelector({ type: 'product' })
pF.filterSelector.a
//               ^? (property) ProductFilter.a: string
const cF = useFilterSelector({ type: 'content' })
cF.filterSelector.b
//               ^? (property) ContentFilter.b: number

Link do playground

Resposta #2

Que tal a asserção de tipo para que o TypeScript use o tipo em vez de unknown?

switch (props.type) {
      case 'product':
        return new ProductFilter(t, queryClient) as FilterSelector;
      case 'content':
        return new ContentFilter(t, queryClient) as FilterSelector;
    }