主要问题在于这个联盟:
type Props = AnimalVersion | CatVersion
Because Props
是一个联盟,setValue
也是一个联盟React.Dispatch<React.SetStateAction<Animal>> | React.Dispatch<React.SetStateAction<Cat>>
.
根据docs:
同样,逆变位置中同一类型变量的多个候选会导致推断交集类型:
这意味着,如果你想调用这样的函数,联合中所有函数的参数将合并(相交),这就是为什么setValue
期望React.SetStateAction<Animal> & React.SetStateAction<Cat>
.
请看简单的例子:
declare var foo: (val: { name: string }) => void
declare var bar: (val: { surname: number }) => void
declare var union:typeof foo | typeof bar
// expects { name: string;} & { surname: number; }
union()
大多数时候,人们都在接受never
, 因为这:
declare var foo: (val: string) => void
declare var bar: (val: number ) => void
declare var union:typeof foo | typeof bar
// expects never
union()
string & number
-> 产生never
,因为这种类型是不可表示的。
在你的情况下,setValue
期望Cat
, 因为Cat
是一个子类型Animal
。超类型的交集Animal
和亚型Cat
- 给你Cat
类型。你有一个错误,因为setValue
期望Cat
然而props.value
可能是一个Animal
, 无需额外 typeOfCat
type.
现在,当我们知道为什么会出现交叉点时,我们就可以继续了。
为了修复它,您可以为Animal|Cat
:
import React from "react";
export interface Animal {
type: string,
age: number
}
export interface Cat extends Animal {
typeOfCat: string,
}
interface AnimalVersion {
value: Animal;
setValue: React.Dispatch<React.SetStateAction<Animal>>;
}
interface CatVersion {
value: Cat;
setValue: React.Dispatch<React.SetStateAction<Cat>>;
}
type Props<T> = {
value: T,
setValue: React.Dispatch<React.SetStateAction<T>>;
}
const ChangeSpecies = <T extends Animal | Cat,>(props: Props<T>): JSX.Element => {
const { value, setValue } = props;
const handleChange = (data: string) => {
setValue({ ...value, type: data });
};
return (
<form>
<label>
<input
type="text"
value={value.type}
onChange={(data) => handleChange(data.target.value)}
/>
</label>
</form>
);
};
const jsx = <ChangeSpecies value={{ type: 'animal', age: 42 }} setValue={(elem /** elem is animal */) => {
}} />
const jsx2 = <ChangeSpecies value={{ type: 'animal', age: 42, typeOfCat: 'cat' }} setValue={(elem /**elem is infered as a cat */) => {
}} />
// error
const expectedError = <ChangeSpecies value={{ type: 'animal', typeOfCat: 'cat' }} setValue={(elem ) => {
}} />
操场
现在,TS 意识到value
具有有效类型setValue
。因为,这两种类型都有type
属性,这条线setValue({ ...value, type: data });
已验证
但是,您应该意识到此解决方案的缺点:
type Props<T> = {
value: T,
setValue: React.Dispatch<React.SetStateAction<T>>;
}
type Result = Props<Animal | Cat>
const invalid: Result = {
value: { type: 'animal', age: 42 },
setValue: (elem /**React.SetStateAction<Animal | Cat> */) => {
},
}
TS还是这么认为elem
是一个联盟。
这意味着如果您在组件构建期间提供显式的联合泛型:
const jsx = <ChangeSpecies<Animal|Cat> value={{ type: 'animal', age: 42 }} setValue={(elem) => {
}} />
TS 会让你做到这一点。
第二个选择
更安全的解决方案是使用分配条件类型:
type Props<T> = T extends any ? {
value: T,
setValue: React.Dispatch<React.SetStateAction<T>>;
} : never
type Result = Props<Animal | Cat>
事实上,这个类型就等于你对 Props union 的表示。它只是以更通用的方式编写。
如果您想坚持使用此解决方案,则应该创建自定义打字保护。这意味着它将影响您的运行时间。同时,可以为值编写类型保护,例如isString
or isNumer
,很难为函数编写类型保护。因为你只能通过引用或arity.length来比较函数。因此,我认为,这是不值得的。