如何基于泛型类型转换功能组件内部的值
How to convert value inside functional component based on generic type
我有以下组件:
// ...
type StringOrNumber = string | number;
type InputProps<T extends StringOrNumber> = {
value: T;
onSubmit: (value: T) => void;
};
export default function Input<T extends StringOrNumber>(props: InputProps<T>) {
const [value, setValue] = useState(props.value.toString());
// Called on enter & blur
const submitValue = () => {
// ts(2345): Argument of type 'string' is not assignable to parameter of type 'T'.
props.onSubmit(value);
};
// ...
return (
<input
{/* ... */}
onChange={(e) => setValue(e.target.value)}
value={value}
/>
);
}
上面 submitValue
中的错误原因很明显,因为 value
在这里总是类型 string
,而 onSubmit
期望 string
或 number
,具体取决于组件的通用 T
。
问题是,我不知道如何正确解决这个错误。我尝试使用类型保护,但导致了同样的错误:
function isNumber(x: StringOrNumber): x is number {
return typeof x === 'number';
}
// ...
export default function Input<T extends StringOrNumber>(props: InputProps<T>): JSX.Element {
// ...
const submitValue = () => {
if (isNumber(props.value)) {
const numberValue = parseFloat(value) || 0;
// ts(2345): Argument of type 'number' is not assignable to parameter of type 'T'.
props.onSubmit(numberValue);
} else {
// ts(2345): Argument of type 'string' is not assignable to parameter of type 'T'.
props.onSubmit(value);
}
};
// ...
}
我想问题是类型保护只在这里检查 props.value
的类型,而它应该以某种方式检查 onSubmit
实际期望的参数类型。
这是如何正确完成的?
问题是 extends StringOrNumber
其中 StringOrNumber
是 string | number
允许 string
和 number
的 子类型 以及这两种类型。字符串文字类型是 string
的子类型,数字文字类型是 number
的子类型。因此,即使您转换为 string
,您也可能不满足 T
上的约束,后者可能比 T
更具体(例如,字符串文字类型 "example"
)。
您的组件不能在一般情况下基于通用类型参数正确转换。那么,三个选项供您选择:
一个相当实用但技术上不正确的类型断言。
也接受道具中的转换功能
使用有区别的联合而不是泛型,所以总是恰好有两种可能性:string
和number
。
一个相当实用但技术上不正确的类型断言
#1 看起来像这样,但在技术上又是不正确的:
// Called on enter & blur
const submitValue = () => {
if (typeof props.value === "string") {
props.onSubmit(String(value) as T);
} else {
props.onSubmit((parseFloat(value) || 0) as T);
}
};
接受转换函数
#2 看起来像这样:
type InputProps<T extends StringOrNumber> = {
value: T;
onSubmit: (value: T) => void;
convert: (value: string) => T;
};
// ...and then when calling `onSubmit`...
const submitValue = () => {
props.onSubmit(props.convert(value));
};
(或者您可以让 onSubmit
接受 string
并在内部进行转换。)
使用有区别的联合
#3 看起来像这样:
type InputProps =
{
__type__: "string",
value: string;
onSubmit: (value: string) => void;
}
|
{
__type__: "number",
value: number;
onSubmit: (value: number) => void;
};
// ...and the component function wouldn't be generic anymore:
export default function Input(props: InputProps) {
// ...
// ...and then when calling `onSubmit`...
const submitValue = () => {
if (props.__type__ === "string") {
props.onSubmit(String(value));
} else {
props.onSubmit((parseFloat(value) || 0));
}
};
我有以下组件:
// ...
type StringOrNumber = string | number;
type InputProps<T extends StringOrNumber> = {
value: T;
onSubmit: (value: T) => void;
};
export default function Input<T extends StringOrNumber>(props: InputProps<T>) {
const [value, setValue] = useState(props.value.toString());
// Called on enter & blur
const submitValue = () => {
// ts(2345): Argument of type 'string' is not assignable to parameter of type 'T'.
props.onSubmit(value);
};
// ...
return (
<input
{/* ... */}
onChange={(e) => setValue(e.target.value)}
value={value}
/>
);
}
上面 submitValue
中的错误原因很明显,因为 value
在这里总是类型 string
,而 onSubmit
期望 string
或 number
,具体取决于组件的通用 T
。
问题是,我不知道如何正确解决这个错误。我尝试使用类型保护,但导致了同样的错误:
function isNumber(x: StringOrNumber): x is number {
return typeof x === 'number';
}
// ...
export default function Input<T extends StringOrNumber>(props: InputProps<T>): JSX.Element {
// ...
const submitValue = () => {
if (isNumber(props.value)) {
const numberValue = parseFloat(value) || 0;
// ts(2345): Argument of type 'number' is not assignable to parameter of type 'T'.
props.onSubmit(numberValue);
} else {
// ts(2345): Argument of type 'string' is not assignable to parameter of type 'T'.
props.onSubmit(value);
}
};
// ...
}
我想问题是类型保护只在这里检查 props.value
的类型,而它应该以某种方式检查 onSubmit
实际期望的参数类型。
这是如何正确完成的?
问题是 extends StringOrNumber
其中 StringOrNumber
是 string | number
允许 string
和 number
的 子类型 以及这两种类型。字符串文字类型是 string
的子类型,数字文字类型是 number
的子类型。因此,即使您转换为 string
,您也可能不满足 T
上的约束,后者可能比 T
更具体(例如,字符串文字类型 "example"
)。
您的组件不能在一般情况下基于通用类型参数正确转换。那么,三个选项供您选择:
一个相当实用但技术上不正确的类型断言。
也接受道具中的转换功能
使用有区别的联合而不是泛型,所以总是恰好有两种可能性:
string
和number
。
一个相当实用但技术上不正确的类型断言
#1 看起来像这样,但在技术上又是不正确的:
// Called on enter & blur
const submitValue = () => {
if (typeof props.value === "string") {
props.onSubmit(String(value) as T);
} else {
props.onSubmit((parseFloat(value) || 0) as T);
}
};
接受转换函数
#2 看起来像这样:
type InputProps<T extends StringOrNumber> = {
value: T;
onSubmit: (value: T) => void;
convert: (value: string) => T;
};
// ...and then when calling `onSubmit`...
const submitValue = () => {
props.onSubmit(props.convert(value));
};
(或者您可以让 onSubmit
接受 string
并在内部进行转换。)
使用有区别的联合
#3 看起来像这样:
type InputProps =
{
__type__: "string",
value: string;
onSubmit: (value: string) => void;
}
|
{
__type__: "number",
value: number;
onSubmit: (value: number) => void;
};
// ...and the component function wouldn't be generic anymore:
export default function Input(props: InputProps) {
// ...
// ...and then when calling `onSubmit`...
const submitValue = () => {
if (props.__type__ === "string") {
props.onSubmit(String(value));
} else {
props.onSubmit((parseFloat(value) || 0));
}
};