簡體   English   中英

具有函數重載和通用參數的 Typescript React 組件

[英]Typescript React component with function overload and generic params

我有一個名為 ResourceSelect 的組件,它基本上是一個普通 Select 組件的包裝器,但使用resources (動態格式,如 {id、name、title})而不是options (帶有 {label、value} 的固定格式)來呈現選擇項. 它在 JavaScript 環境中工作,但我發現自己無法為它實現一個好的打字稿定義。 目前我可以做的是使用函數重載和泛型參數來實現這樣的直接用法:


export type ValueableType = number | string;

export type DefaultResourceType = Record<string, string | number>;

export interface BaseResourceSelectProps<
  RT extends DefaultResourceType = DefaultResourceType,
  VT extends ValueableType | number[] | string[] = number
> {
  /** resource list used to render options */
  resources?: RT[];
  /** the default key to render label */
  labelKey?: keyof RT;

  // In my real implementation it's from extends AntdSelectProps<VT>
  // But I think value and onChange can represent this part
  value?: VT;
  onChange?: (value: VT) => void;
}

export interface GenericResourceSelectProps extends BaseResourceSelectProps {
  mode?: 'multiple';
  valueKey?: string;
}


export function ResourceSelect<
  RT extends DefaultResourceType = DefaultResourceType,
  VK extends keyof RT = 'id'
>(
  props: {
    valueKey?: VK;
    mode?: never;
  } & BaseResourceSelectProps<RT, RT[VK]>
): JSX.Element;

export function ResourceSelect<
  RT extends DefaultResourceType = DefaultResourceType,
  VK extends keyof RT = 'id'
>(
  props: {
    valueKey?: VK;
    mode: 'multiple';
  } & BaseResourceSelectProps<RT, RT[VK] extends string ? string[] : number[]>
): JSX.Element;

/**
 * ResourceSelect
 */
export function ResourceSelect(genericProps: unknown) {
  const props = genericProps as 
  // some implementation
}


// Storybook cases

export const CodeBoard: Story<{
  value: number | string | number[] | string[];
  onSelect: (v: unknown) => void;
}> = ({ value, onSelect }) => {
  return (
    <div style={{ maxWidth: 200 }}>
      <h3>Normal usage</h3>
      <ResourceSelect
        resources={resources}
        value={value as number}
        defaultValue={1}
        onChange={(num: number) => onSelect(num)}
        style={{ width: '100%' }}
      />

      <h3>Custome valueKey</h3>
      <ResourceSelect
        resources={resources}
        value={value as string}
        valueKey="strId"
        defaultValue="id 1"
        onChange={(str: string) => onSelect(str)}
        style={{ width: '100%' }}
      />

      <h3>Multiple mode</h3>
      <ResourceSelect
        resources={resources}
        value={value as number[]}
        defaultValue={[1]}
        mode="multiple"
        onChange={(numArr: number[]) => onSelect(numArr)}
        style={{ width: '100%' }}
      />
    </div>
  );
};

DesignBoard.args = {
  value: undefined,
};

DesignBoard.argTypes = { onSelect: { action: 'onSelect' } };

我現在的問題是我無法重新封裝這個組件。 像這樣的代碼不起作用:


type ResourceSelectProps = Parameters<typeof ResourceSelect>[0];

interface Location {
  id: number;
  address: string;
}

/**
 * LocationSelect
 */
export const LocationSelect: FC<ResourceSelectProps<Location>> = (props) => {
  // Call an API hook that fetches locations
  const { data = [], isValidating } = useLocations();

  return (
    <ResourceSelect
      resources={data}
      placeholder="Location"
      loading={isValidating}
      labelKey="address"
      dropdownMatchSelectWidth={false}
      {...props}
    />
  );
};

我自己想通了,但仍然無法在封裝的組件中添加默認值:

import { PropsWithChildren, ReactNode } from 'react';
import { SelectProps } from 'antd/es/select';
export type ValueableType = number | string;

export type ModeType = 'multiple' | 'single';

export type DefaultResourceType = Record<string, ValueableType>;

export type ResourceSelectKeys = 'resources' | 'mode';

export interface BaseResourceSelectProps<
  RT extends DefaultResourceType = DefaultResourceType,
  LK extends keyof RT = 'name',
  VT = ValueableType
> extends SelectProps<VT> {
  /** resource list used to render options */
  resources?: RT[];
  /** the default key to render label */
  labelKey?: LK;
  /** custom option renderer */
  renderOption?: (resource: RT) => ReactNode;
  /** custom label renderer */
  renderLabel?: (resource: RT) => ReactNode;
}

export type ResourceSelectWithModeProps<
  RT extends DefaultResourceType = DefaultResourceType,
  VT = ValueableType,
  LK extends keyof RT = 'name',
  OKS extends ResourceSelectKeys = never
> =
  | ({
      mode: 'multiple';
    } & Omit<BaseResourceSelectProps<RT, LK, VT[]>, OKS>)
  | ({
      mode?: never;
    } & Omit<BaseResourceSelectProps<RT, LK, VT>, OKS>);

export type ResourceSelectProps<
  RT extends DefaultResourceType = DefaultResourceType,
  VK extends keyof RT = 'id',
  LK extends keyof RT = 'name',
  OKS extends ResourceSelectKeys = never
> =
  | ({
      valueKey?: never;
    } & ResourceSelectWithModeProps<RT, RT['id'], LK, OKS>)
  | ({
      valueKey: VK;
    } & ResourceSelectWithModeProps<RT, RT[VK], LK, OKS>)
  | ({
      valueKey?: VK[];
    } & ResourceSelectWithModeProps<RT, Pick<RT, VK>, LK, OKS>);

像這樣的代碼仍然無法正常工作:


export const countries = [
  { name: 'Afghanistan', code: 'AF' },
  { name: 'Åland Islands', code: 'AX' },
  { name: 'Albania', code: 'AL' },
];
type Country = typeof countries[number];

/**
 * CountrySelect
 */
export const CountrySelect: FC<ResourceSelectProps<Country, 'code'>> = (
  props
) => {
  return (
    <ResourceSelect
      placeholder="Country"
      resources={countries}
      {...props}
      valueKey={props.valueKey || 'code'}
    />
  );
};

在此處輸入圖像描述

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM