[英]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.