这是我的代码:
import classNames from 'classnames';
import { uniqueId } from 'lodash';
import React, { forwardRef, useCallback, useMemo } from 'react';
interface BaseInputProps {
className?: string;
placeholder?: string;
label?: string;
suffix?: string;
disabled?: boolean;
autoFocus?: boolean;
}
type TextInputProps = {
type?: 'search' | 'text';
value?: string;
} & BaseInputProps &
OnChangeProps<string>;
type NumberInputProps = {
type: 'number';
value?: number;
} & BaseInputProps &
OnChangeProps<number>;
type OnChangeProps<T> =
| {
isForwarded: true;
onChange?: (event: Event) => void;
}
| {
isForwarded?: false;
onChange?: (value: T) => void;
};
const Input = forwardRef<HTMLInputElement, TextInputProps | NumberInputProps>(
(
{
className,
isForwarded,
placeholder,
label,
suffix,
disabled,
autoFocus,
...props
},
ref
) => {
const handleOnChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement> | Event) => {
if (isForwarded) {
props.onChange?.(event as Event);
} else if (props.type === 'number') {
props.onChange?.(event.target.valueAsNumber);
} else {
props.onChange?.(event.target.value);
}
},
[props.onChange, props.type]
);
const htmlId = useMemo(() => uniqueId('input_'), []);
return (
<div
className={classNames(className, 'flex flex-col items-stretch gap-1')}
>
{label && (
<label
htmlFor={htmlId}
className="mr-2 select-none font-medium text-sm text-carbon-800"
>
{label}
</label>
)}
<div className="flex flex-row items-center h-9 px-2 border-[1.5px] border-gray-200 rounded-md overflow-hidden hover:border-gray-300 focus-within:border-gray-300">
<input
id={htmlId}
ref={ref}
onChange={handleOnChange}
type={props.type}
value={props.value}
placeholder={placeholder || ''}
className={classNames(
className
?.split(' ')
.filter((c) => c.includes('bg-') || c.includes('text-'))
.join(' '),
'inline-block border-none w-full p-0 text-sm placeholder:text-gray-400 focus:ring-0'
)}
disabled={disabled}
autoFocus={autoFocus}
/>
{suffix && (
<span className="text-sm ml-2 text-carbon-600">{suffix}</span>
)}
</div>
</div>
);
}
);
Input.displayName = 'Input';
export default Input;
上面的组件需要事件的类型为
Event
,因此有奇怪的条件类型。
我不明白为什么
props.onChange?.(event as Event);
正在触发以下错误:
- “Event”类型的参数不可分配给“never”类型的参数。 [2345]
你有什么线索吗?
您希望通过检查
isForwarded
和 props.type
,TypeScript 将 narrow props.onChange
成为一个需要适当参数类型的函数。但这并没有发生; props.onChange
仍然是函数的 union,因此只能使用参数类型的 intersection 安全地调用(请参阅对调用函数并集的支持),并且由于 string
和 number
没有共同居民,你得到never
类型。哎呀。
forwardRef()
的第一个参数是一个可区分的联合类型,如果你将其保留为这样,则可以检查其区分属性:
const Input = forwardRef<HTMLInputElement, TextInputProps | NumberInputProps>((
arg, ref) => {
const handleOnChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement> | Event) => {
if (arg.isForwarded) {
arg.onChange?.(event as Event); // okay
} else if (arg.type === 'number') {
arg.onChange?.((event as React.ChangeEvent<HTMLInputElement>).target?.valueAsNumber); // okay
} else {
arg.onChange?.((event as React.ChangeEvent<HTMLInputElement>).target?.value); // okay
}
},
[arg.onChange, arg.type]
);
但是你已经将其解构成变量了。再说一次,如果你完全这样做了,它也会起作用,因为 TypeScript 支持缩小解构的可区分联合:
const Input = forwardRef<HTMLInputElement, TextInputProps | NumberInputProps>(({
className, isForwarded, placeholder, label,
suffix, disabled, autoFocus, onChange,
type, value
}, ref) => {
const handleOnChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement> | Event) => {
if (isForwarded) {
onChange?.(event as Event); // okay
} else if (type === 'number') {
onChange?.((event as React.ChangeEvent<HTMLInputElement>).target?.valueAsNumber); // okay
} else {
onChange?.((event as React.ChangeEvent<HTMLInputElement>).target?.value); // okay
}
},
[onChange, type]
);
请注意,您仍然可以拥有
...props
剩余属性;它只是不会参与缩小。所以你只需要“完全”解构到缩小的所有参与者都是他们自己的变量即可:
const Input = forwardRef<HTMLInputElement, TextInputProps | NumberInputProps>(({
className, isForwarded, placeholder, label,
suffix, disabled, autoFocus, onChange,
type, ...props // <-- still usable
}, ref) => {
const handleOnChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement> | Event) => {
if (isForwarded) {
onChange?.(event as Event); // okay
} else if (type === 'number') {
onChange?.((event as React.ChangeEvent<HTMLInputElement>).target?.valueAsNumber); // okay
} else {
onChange?.((event as React.ChangeEvent<HTMLInputElement>).target?.value); // okay
}
},
[onChange, type]
);
这两种方法都适合您。
目前不起作用的,是使用rest属性通过partial解构来缩小范围。这是 microsoft/TypeScript#46680 中请求的 TypeScript 缺少的功能。如果实现了这一点,那么您的代码可能会开始工作。但除非发生这种情况,否则您需要以某种方式解决它,例如上述两种方法之一。