包含不同类型的任一类型
Either type containing different types
[原始 post 编辑为简化示例]
我想创建一个组件,它将采用一对值(两个值基于相同的类型
-> value
、setValue
),并且基于它,运行 将根据这些类型发挥作用。充分说明情况:
AnimalAndCatTypes.ts:
export interface Animal {
type: string,
age: number
}
export interface Cat extends Animal {
typeOfCat: string,
}
ChangeSpecies.tsx:
import React from "react";
import { Animal, Cat } from "./AnimalAndCatTypes";
interface AnimalVersion {
value: Animal;
setValue: React.Dispatch<React.SetStateAction<Animal>>;
}
interface CatVersion {
value: Cat;
setValue: React.Dispatch<React.SetStateAction<Cat>>;
}
type Props = AnimalVersion | CatVersion;
const ChangeSpecies = (props: Props): 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>
);
};
export default ChangeSpecies;
ExampleComponent.tsx:
import React, { useState } from "react";
import { Animal, Cat } from "./AnimalAndCatTypes";
import ChangeSpecies from "./ChangeSpecies";
const ExampleComponent = (): JSX.Element => {
const [animal, setAnimal] = useState<Animal>({
type: "dog",
age: 2
});
const [cat, setCat] = useState<Cat>({
type: "cat",
age: 1,
typeOfCat: "persian"
});
return (
<div>
<ChangeSpecies value={animal} setValue={setAnimal} />
<ChangeSpecies value={cat} setValue={setCat} />
</div>
);
};
export default ExampleComponent;
问题出在 setValue
,打字稿说 React.Dispatch<React.SetStateAction<Cat>>
中不能使用 Animal
因为缺少 typeOfCat
.
如何创建一个类型,让 ts 在创建组件时通过检查 value
和 [=14= 知道 Props
是 Cat
或 Animal
] 来自一对。稍后,根据那对类型,将采用链接到 value
类型且不能与其他类型混合的正确 setValue
类型?
[编辑中添加]
Link转载:https://codesandbox.io/s/zealous-mirzakhani-1x4kz?file=/src/App.js
主要问题在这个联合中:
type Props = AnimalVersion | CatVersion
因为Props
是并集,所以setValue
也是React.Dispatch<React.SetStateAction<Animal>> | React.Dispatch<React.SetStateAction<Cat>>
的并集。
根据 docs:
Likewise, multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred:
这意味着,如果你想调用这样的函数,联合中所有函数的参数将合并(相交),这就是为什么 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
类型。
现在,当我们知道为什么会出现交叉点时,我们就可以继续了。
为了修复它,您可以为 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
的有效类型。 SInce,两种类型都有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 会让你做到。
第二个选项
更安全的解决方案是使用 distributive-conditional-types:
type Props<T> = T extends any ? {
value: T,
setValue: React.Dispatch<React.SetStateAction<T>>;
} : never
type Result = Props<Animal | Cat>
其实这个类型就等于你表示的Props union。它只是以更通用的方式编写的。
如果您想坚持使用此解决方案,则应创建 custom typeguard。这意味着它将影响您的运行时间。虽然,可以为值编写类型保护,如 isString
或 isNumer
,但很难为函数编写类型保护。因为您只能通过引用或 arity.length 来比较函数。所以,我觉得,不值得。
[原始 post 编辑为简化示例]
我想创建一个组件,它将采用一对值(两个值基于相同的类型
-> value
、setValue
),并且基于它,运行 将根据这些类型发挥作用。充分说明情况:
AnimalAndCatTypes.ts:
export interface Animal {
type: string,
age: number
}
export interface Cat extends Animal {
typeOfCat: string,
}
ChangeSpecies.tsx:
import React from "react";
import { Animal, Cat } from "./AnimalAndCatTypes";
interface AnimalVersion {
value: Animal;
setValue: React.Dispatch<React.SetStateAction<Animal>>;
}
interface CatVersion {
value: Cat;
setValue: React.Dispatch<React.SetStateAction<Cat>>;
}
type Props = AnimalVersion | CatVersion;
const ChangeSpecies = (props: Props): 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>
);
};
export default ChangeSpecies;
ExampleComponent.tsx:
import React, { useState } from "react";
import { Animal, Cat } from "./AnimalAndCatTypes";
import ChangeSpecies from "./ChangeSpecies";
const ExampleComponent = (): JSX.Element => {
const [animal, setAnimal] = useState<Animal>({
type: "dog",
age: 2
});
const [cat, setCat] = useState<Cat>({
type: "cat",
age: 1,
typeOfCat: "persian"
});
return (
<div>
<ChangeSpecies value={animal} setValue={setAnimal} />
<ChangeSpecies value={cat} setValue={setCat} />
</div>
);
};
export default ExampleComponent;
问题出在 setValue
,打字稿说 React.Dispatch<React.SetStateAction<Cat>>
中不能使用 Animal
因为缺少 typeOfCat
.
如何创建一个类型,让 ts 在创建组件时通过检查 value
和 [=14= 知道 Props
是 Cat
或 Animal
] 来自一对。稍后,根据那对类型,将采用链接到 value
类型且不能与其他类型混合的正确 setValue
类型?
[编辑中添加]
Link转载:https://codesandbox.io/s/zealous-mirzakhani-1x4kz?file=/src/App.js
主要问题在这个联合中:
type Props = AnimalVersion | CatVersion
因为Props
是并集,所以setValue
也是React.Dispatch<React.SetStateAction<Animal>> | React.Dispatch<React.SetStateAction<Cat>>
的并集。
根据 docs:
Likewise, multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred:
这意味着,如果你想调用这样的函数,联合中所有函数的参数将合并(相交),这就是为什么 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
类型。
现在,当我们知道为什么会出现交叉点时,我们就可以继续了。
为了修复它,您可以为 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
的有效类型。 SInce,两种类型都有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 会让你做到。
第二个选项
更安全的解决方案是使用 distributive-conditional-types:
type Props<T> = T extends any ? {
value: T,
setValue: React.Dispatch<React.SetStateAction<T>>;
} : never
type Result = Props<Animal | Cat>
其实这个类型就等于你表示的Props union。它只是以更通用的方式编写的。
如果您想坚持使用此解决方案,则应创建 custom typeguard。这意味着它将影响您的运行时间。虽然,可以为值编写类型保护,如 isString
或 isNumer
,但很难为函数编写类型保护。因为您只能通过引用或 arity.length 来比较函数。所以,我觉得,不值得。