减速器动作中的ngrx有效负载未编译

Ziv*_*zer 8 typescript ngrx angular typescript2.4 ngrx-store-4.0

我正在尝试将我的角度2应用程序迁移到角度4和ngrx 4.

我正在处理一个奇怪的打字稿编译问题,这是我在此次更新之前没有的.我在离线环境中工作,所以我不能在这里分享确切的代码.

我在网上寻找问题,我能找到的最接近的问题就是这个问题: 关于有效载荷类型 的问题这个问题中的代码与我试图做的有些相似,但区别在于我从动作中删除了ActionTypes对象(这是该问题的建议答案)但它没有解决我的编译错误.

我不能把确切的代码我解决这里,但我完全基于github上的ngrx示例ngrx官方示例我的代码

让我们看一下书籍减速机和书籍动作书动作 书籍减速机的例子

我的代码基本相同,当我尝试从操作中获取有效负载时,每个reducer情况(在交换机情况下)都会发生错误,它告诉我类似于:

Type 'string | Book | Book[]' is not assignable to type 'Book'. Type 'string' is not assignable to type 'Book'.
Run Code Online (Sandbox Code Playgroud)

(在我的代码中,而不是Book我有不同的类,但它的想法几乎相同)

现在我通过从A |中转换动作类型来解决错误 B | B是与交换机中的情况相对应的特定动作类型(使用as typescript关键字).这个解决方案感觉很糟糕和hacky,我想知道是否有更好的解决方案.

我使用的版本:typescript:2.4.1(我怀疑它可能连接到ngrx示例,不使用与我相同的更新的打字稿版本)ngrx @ store:4.0.3 ngrx @ core:1.2.0 angular :(最新的17.9.17,角度内有很多包,我不认为它对于这个错误使用哪个角度非常重要,但我说这只是为了安全方面)打字稿:(2.4.1)(我读了ngrx关于需要升级打字稿的官方迁移文档,所以我做了)


更新:由于我得到了一个与我的问题无关的答案,而且我也被问过,这是我的代码中的一部分失败了(我无法发布确切的帖子,但我手工复制相关部分):

任务reducer.ts:

import * as MissionActionsFile from "./mission-actions";

export type State = .... // doesnt matter

const initialState: State = // doesnt matter as well

export function reducer(state: State = initialState, action: 
    MissionActionsFile.Actions): State {

    switch(action.type) {

        case MissionActionsFile.ADD_SUCCESS: 
        {
            const mission: Mission = action.payload; // This line generates the 
            // following error: 
            // Type 'string | number | Mission | MissionRealTime | ...' is not 
            // assignable 
            // to type 'Mission'. Type 'string' is not assignable to type 'Mission'

            return {}; // This doesnt matter too
        }

        // There are more cases and a deafult one, but that doesn't matter
    }
}
Run Code Online (Sandbox Code Playgroud)

任务actions.ts:

// I will only give the actual action defenition for AddSuccess and for AddFailed since we are using AddSuccess in the example and 
// addFailed is an example where the payload type is string, which according to the error is the problem, though
// there are more action types and other payload types with other classes as the quoted error implies.

export const ADD_SUCCESS: string = "[Mission] AddSuccess";
export const ADD_FAILED: string = "[Mission] AddFailed";

export class AddSuccessAction implements Action {
    public readonly type: string = ADD_SUCCESS;

    constructor(public payload: Mission) {}
}

export class AddFailedAction implements Action {
    public readonly type:string = ADD_FAILED;

    constructor(public payload: string) {}
}

// More actions are defined here but im not gonna copy all

...

export type Actions =
    AddSuccessAction |
    AddFailedAction;
Run Code Online (Sandbox Code Playgroud)

总而言之,我确实看到为什么对于打字稿来说认为动作有效负载类型可能是字符串|是有意义的 使命| ...但是在我基于此的ngrx示例中,似乎打字稿知道在那里推断出案件中的具体类型,但对于我不能理解我不理解的原因,可能与我有关使用打字稿2.4.1?不确定,需要帮助

Alu*_*dad 11

问题是代码的行为及其类型注释彼此交叉.

实际上,我会说代码被过度注释.

通过基于类型推断和基于控制流的类型分析实现联合类型和案例分析,这是TypeScript最强大的功能之一.类型检查器消除联合可能性的过程称为缩小.

如代码所示,通过对称为判别式的属性执行值测试,可以将联合类型分解为其组成部分.

判别式是一种属性,其类型具有有限的可能值集,每个值通常对应于并集的情况.

string不是有效的判别,但是"hello world".(作为所有可能字符串的超类型,字符串类型的联合,包括string折叠到仅string)

当我们在TypeScript中定义一个string | "hello world"或一个string属性时,编译器会将其类型推断为初始化文字的类型.

考虑:

const kind = "first";
Run Code Online (Sandbox Code Playgroud)

这里的类型const是不是readonly,但kind.

同样,给定

class Kindred {
  readonly kind = "first";
}
Run Code Online (Sandbox Code Playgroud)

该属性的类型string是不是"first",但kind.

在我们的代码中,判别式是一个名为的属性string,由联盟的每个成员定义.

但是,虽然您已为每个成员正确提供了唯一值,但您已使用类型注释过度指定它们以防止缩小.

你有什么:

export class AddSuccessAction {
    public readonly type: string = ADD_SUCCESS;

    constructor(public payload: Mission) {}
}

export class AddFailedAction {
    public readonly type: string = ADD_FAILED;

    constructor(public payload: string) {}
}
Run Code Online (Sandbox Code Playgroud)

你想要什么:

export class AddSuccessAction {
    readonly type = ADD_SUCCESS;

    constructor(public payload: Mission) {}
}

export class AddFailedAction {
    readonly type = ADD_FAILED;

    constructor(public payload: string) {}
}
Run Code Online (Sandbox Code Playgroud)

一个工作 "first"

switch (action.type) {
  // action.type is `"[Mission] AddSuccess" | "[Mission] AddFailed"`
  // action.payload is `string | Mission`
  case missionActions.ADD_SUCCESS:
    // action.type is `"[Mission] AddSuccess"`
    // action.payload is `Mission`
    const mission = action.payload;
}
Run Code Online (Sandbox Code Playgroud)

为什么这很重要:

字符串文字类型是区分联合可能性的常用和惯用方法,但是,通过将实现属性声明为类型type,类型是所有字符串文字类型的超类型,我们禁止类型推断,从而防止缩小.请注意,这switch不是可以缩小的类型.

通常,当值具有初始值设定项时,最好利用TypeScript的类型推断.除了启用预期方案之外,类型推理器将捕获的错误数量将给您留下深刻印象.我们不应该告诉编译器它需要知道的东西,除非我们故意想要一个比它推断的更通用的类型.有了string它,我们总是会告诉我们什么时候需要以类型注释的形式指定额外的信息.

备注:

技术上可以指定类型,并仍通过指定文字值的类型保留缩小行为string--noImplicitAny财产.然而,这增加了维护成本并且相当多余.

例如,以下内容有效:

export const ADD_FAILED: "[Mission] AddFailed" = "[Mission] AddFailed";
Run Code Online (Sandbox Code Playgroud)

但你只是在不必要地重复自己.

在他们的回答中,ilyabasiuk提供了一些很好的参考链接,特别是与ngrx一起使用文字类型,以及它是如何在最近的TypeScript语言迭代中进化的.

要理解为什么在不可变位置推断文字类型,请考虑它可以实现强大的静态分析,从而更好地检测错误.

考虑:

type Direction = "N" | "E" | "S" | "W";

declare function getDirection(): Direction;

const currentDirection = getDirection();

if (currentDirection === "N") { // currentDirection is "N" | "E" | "S" | "W"

}
// Error: this is impossible, unreachable code as currentDirection is "E" | "S" | "W"
// the compiler knows and will give us an error here.
else if (currentDirection === "N") {

}
Run Code Online (Sandbox Code Playgroud)