我最近学习了打字稿,我遇到了一个错误,我认为我应该为它编写更窄的类型或类似的东西。
这是我写的代码:
enum ElementTypes {
h1 = 'H1',
word = "WORD"
}
type DefaultElementType<T> = {
children: string[];
id: number | string;
type: T;
};
type H1Element = DefaultElementType<ElementTypes.h1>;
type WordElement = DefaultElementType<ElementTypes.word> & {
value: string;
}
type CustomElement =
| H1Element
| WordElement
const insertNode = (element: CustomElement) => {
// do sth here, not important
}
const addInsertNode = ({type, id}: {type: ElementTypes, id: number | string}) => {
insertNode({type, id, children:[], ...(type === ElementTypes.word ? {value: 'some value'}: {})})
}
这是它给出的错误:
Argument of type '{ value?: string | undefined; type: ElementTypes; id: string | number; children: never[]; }' is not assignable to parameter of type 'CustomElement'.
Type '{ value?: string | undefined; type: ElementTypes; id: string | number; children: never[]; }' is not assignable to type 'WordElement'.
Type '{ value?: string | undefined; type: ElementTypes; id: string | number; children: never[]; }' is not assignable to type 'DefaultElementType<ElementTypes.word>'.
Types of property 'type' are incompatible.
Type 'ElementTypes' is not assignable to type 'ElementTypes.word'.
我尝试过写受歧视的工会,但没有帮助。
这是错误的游乐场。
我将不胜感激任何帮助。
TypeScript 仅分析每一位代码一次。如果某个表达式多次提及同一值,并且该值属于“联合类型”,则编译器无法分析该联合的每个成员的表达式。相反,它分析一次,并独立处理联合类型的每个值。如果您有一个类型为 v
的值
X | Y
,那么编译器会认为 [v, v]
具有类型 [X | Y, X | Y]
(概念上类似于 [X, X] | [X, Y] | [Y, X] | [Y, Y]
),而 not
[X, X] | [Y, Y]
。 TypeScript 无法直接处理相关联合。这是 microsoft/TypeScript#30581 的主题。 里面
addInsertNode()
,表情
{
type, id, children: [],
...(type === ElementTypes.word ? { value: 'some value' } : {})
}
有两次提到
type
,它属于联合类型
ElementTypes
。因此,完整的表达式是类似 { type: ElementTypes } & ({value: string} | {})
的类型,它失去了 type
和展开对象之间的相关性。因此编译器无法验证该值是否可分配给 CustomElement
,并且您会收到错误。
这里有多种方法可以进行。您始终可以“断言”您的代码是正确的。这是最简单的方法,可以让运行时代码保持原样:const addInsertNode = ({ type, id }: { type: ElementTypes, id: number | string }) => {
insertNode({
type, id, children: [],
...(type === ElementTypes.word ? { value: 'some value' } : {})
} as CustomElement)
}
编译器不再验证代码的安全性,但它既快速又容易做到。
narrowing
来区分type
的类型。像这样:
const addInsertNode = ({ type, id }: { type: ElementTypes, id: number | string }) => {
insertNode(
(type === ElementTypes.word) ?
{ type, id, children: [], value: 'some value' } :
{ type, id, children: [] }
)
}
{type, id, children: []
部分。但它是有效的,因为现在编译器可以将
type
明确地视为 true 分支中的
ElementTypes.word
,如果是 false 分支中的 ElementTypes.h1
。每个表达式都有自己明确定义的类型。
另一种方法是保留单一表达式,但尝试用泛型而不是联合来表示事物。
中详细描述了此技术。技巧是编写一个基本的键值映射类型,然后用该类型表示所有操作,或者用泛型 索引到 类型,或者泛型索引到 映射类型 中。
基本类型如下所示:
interface ExtraProps {
[ElementTypes.h1]: {},
[ElementTypes.word]: { value: string; }
}
然后你可以将
CustomElement
重写为分布式对象类型
:
type CustomElement<K extends ElementTypes = ElementTypes> =
{ [P in K]: DefaultElementType<P> & ExtraProps[P] }[K]
它是通用的,因此 CustomElement<ElementTypes.h1>
是您
H1Element
的新名称,而
CustomElement<ElementTypes.word>
是您 WordElement
的新名称。并且 CustomElement
本身 defaults为
CustomElement<ElementTypes>
,这相当于您原来的 CustomElemen
联合:// demo that it's the same
type CE = CustomElement
/* type CE = DefaultElementType<ElementTypes.h1> | (DefaultElementType<ElementTypes.word> & {
value: string;
}) */
现在我们需要使 insertNode
变得通用:
const insertNode = <K extends ElementTypes>(element: CustomElement<K>) => {
// do sth here, not important
}
addInsertNode()
也需要是通用的:
const addInsertNode = <K extends ElementTypes>({ type, id }: { type: K, id: number | string }) => {
insertNode({
type, id, children: [],
...{ [ElementTypes.h1]: {}, [ElementTypes.word]: { value: "some value" } }[type]
})
}
(type === ElementTypes.word ? { value: 'some value' } : {})
替换为等效的
{ [ElementTypes.h1]: {}, [ElementTypes.word]: { value: "some value" } }[type]
。它执行对象查找,而不是条件。该对象查找与
ExtraProps[K]
完全匹配,这让编译器可以将 type
的两种可能性视为单个操作。此重构有效,但对于您的用例来说可能不值得。有时很容易断言并继续,或者编写一些更冗余的代码来利用缩小的优势。但至少可以让编译器遵循这种逻辑。Playground 代码链接