类型“ElementTypes”不可分配给类型“ElementTypes.word”

问题描述 投票:0回答:1

我最近学习了打字稿,我遇到了一个错误,我认为我应该为它编写更窄的类型或类似的东西。

这是我写的代码:

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
1个回答
0
投票

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
。每个表达式都有自己明确定义的类型。

另一种方法是保留单一表达式,但尝试用
泛型

而不是联合来表示事物。
microsoft/TypeScript#47109

中详细描述了此技术。技巧是编写一个基本的键值映射类型,然后用该类型表示所有操作,或者用泛型 索引到 类型,或者泛型索引到 映射类型 中。 基本类型如下所示: 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 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.