[React Typescript] Discriminated unions in components props
import React, { useState } from "react";
type Base = { id: string } | string;
type GenericSelectProps<TValue> = {
formatLabel: (value: TValue) => string;
values: Readonly<TValue[]>;
};
interface SingleSelectProps<TValue> extends GenericSelectProps<TValue> {
isMulti: false;
onChange: (value: TValue) => void;
}
interface MultiSelectProps<TValue> extends GenericSelectProps<TValue> {
isMulti: true;
onChange: (value: TValue[]) => void;
}
type T = { id: string };
const isStringValue = <TValue extends T>(
value: TValue | string
): value is string => {
return typeof value === "string";
};
const getStringFromValue = <TValue extends Base>(value: TValue) => {
if (isStringValue(value)) return value;
return value.id;
};
export const GenericSelect = <TValue extends Base>(
props: SingleSelectProps<TValue> | MultiSelectProps<TValue>
) => {
const { values, formatLabel } = props;
const [selectedValues, setSelectedValues] = useState<TValue[]>([]);
const onSelectChange = (e) => {
const val = values.find(
(value) => getStringFromValue(value) === e.target.value
);
if (!val) return;
if (props.isMulti) {
const newValues = [...selectedValues, val];
props.onChange(newValues);
setSelectedValues(newValues);
} else {
if (val) props.onChange(val);
}
};
const onDeleteValue = (index: number) => {
return () => {
const newValues = selectedValues.filter((v, i) => i !== index);
if (props.isMulti) {
props.onChange(newValues);
setSelectedValues(newValues);
}
};
};
const visibleValues = !props.isMulti
? values
: values.filter((v) => !selectedValues?.includes(v));
return (
<>
{props.isMulti ? (
<>
{selectedValues.map((v, index) => (
<span onClick={onDeleteValue(index)}>{formatLabel(v)} [x]</span>
))}
<br />
</>
) : (
""
)}
<select onChange={onSelectChange} multiple={props.isMulti}>
{visibleValues.map((value) => (
<option
key={getStringFromValue(value)}
value={getStringFromValue(value)}
>
{formatLabel(value)}
</option>
))}
</select>
</>
);
};
Usage:
const select = (
<GenericSelect<Book>
// I can't log "value.title" here, typescript will fail
// property "title" doesn't exist on type "Book[]""
// even if I know for sure that this is a single select
// and the type will always be just "Book"
onChange={(value) => console.info(value.title)}
isMulti={false}
...
/>
);
const multiSelect = (
<GenericSelect<Book>
// I can't iterate on the value here, typescript will fail
// property "map" doesn't exist on type "Book"
// even if I know for sure that this is a multi select
// and the type will always be "Book[]"
onChange={(value) => value.map(v => console.info(v))}
isMulti={true}
...
/>
);
Refer: https://www.developerway.com/posts/advanced-typescript-for-react-developers-discriminated-unions
分类:
React
, TypeScript
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
2021-11-21 [QROQ] Query Language
2019-11-21 [HTML5] Using HTMLPortalElement to improve MPA preformance
2019-11-21 [CSS] prefers-reduced-motion
2018-11-21 [Testing] Config jest to test Javascript Application -- Part 2
2017-11-21 [ES6] The Iterator Protocol
2017-11-21 [Python] for.. not in.. Remove Deduplication
2017-11-21 [Python] String Join