打字稿React组件具有功能超载和通用参数

发布于 2025-01-31 23:15:43 字数 3544 浏览 5 评论 0原文

我有一个名为ResourceElect的组件,它基本上是一个普通选择组件上的包装器,但是使用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}
    />
  );
};

I have a component named ResourceSelect, it's basically a wrapper over a normal Select component, but uses resources(dynamic format like {id, name, title}) instead of options(fixed format with {label, value}) to render the select items. It worked in JavaScript environment, but I found myself cannot implement a good typescript definition for it. Currently I can do is using function overload and generic params to achieve directly usage like this:


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' } };

My problem now is that I cannot re-encapsulate this component. Code like this just not working:


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}
    />
  );
};

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

別甾虛僞 2025-02-07 23:15:43

我自己想出了自己的想法,但仍然无法在封装的组件中添加默认值:

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'}
    />
  );
};

“在此处输入图像说明”

I kind of figured it out myself, but still not be able to add default values in the encapsulated component:

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>);

Code like this still not working:


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'}
    />
  );
};

enter image description here

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文