angular - reducer Action 中的 ngrx 有效负载未编译

标签 angular typescript ngrx typescript2.4 ngrx-store-4.0

我正在尝试将我的 Angular 2 应用程序迁移到 Angular 4 和 ngrx 4。

我正在解决一个奇怪的 typescript 编译问题,这是我在这次更新之前没有遇到过的。我在离线环境中工作,所以我不能在这里分享确切的代码。

我在网上找了这个问题,我能找到的最接近的是这个问题: question about payload types 这个问题中的代码与我试图做的有点相似,但不同之处在于我从操作中删除了 ActionTypes 对象(这是该问题的建议答案)但它没有解决我的编译错误。

我不能把我处理的确切代码放在这里,但我的代码完全基于 ngrx 示例 ngrx official example on github

让我们以 book reducer 和 book actions 为例 book actions book reducer

我的代码基本上是一样的,当我试图从 action 中获取 payload 时,每个 reducer case(在 switch case 中)都会出现错误, 它告诉我类似这样的事情:

Type 'string | Book | Book[]' is not assignable to type 'Book'. Type 'string' is not assignable to type 'Book'.

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

现在我正在通过转换 A | 中的 Action 类型来解决错误乙 |乙 成为与开关中的案例对应的特定操作类型(使用 as typescript 关键字)。 这个解决方案感觉很糟糕而且很hacky,我想知道是否有更好的解决方案。

我使用的版本: typescript :2.4.1(我怀疑它可能连接到未使用与我相同的更新 typescript 版本的 ngrx 示例) ngrx@商店:4.0.3 ngrx@核心:1.2.0 angular:(最新的 17.9.17,angular 中有很多包,我认为我使用哪个 angular 来解决这个错误并不重要,但我说这只是为了安全起见) typescript :(2.4.1)(我阅读了ngrx官方迁移文档关于需要升级 typescript ,所以我做了)


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

mission-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
    }
}

任务 Action .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;

总而言之,我明白为什么 typescript 认为 Action 负载类型可能是 string |使命 | ... 但是在我基于此的 ngrx 示例中,似乎 typescript 知道在那里可以推断出这种情况下的特定类型,但对我来说,由于我不理解的原因,这不起作用,可能与我使用 typescript 2.4 有关.1?不确定,需要这方面的帮助

最佳答案

问题是代码的行为和它的类型注释是相互交叉的。

实际上,我会说代码注释过多。

联合类型和它们支持的案例分析通过类型推断和基于控制流的类型分析来工作,这是 TypeScript 最强大的两个功能。语言从联合中消除可能性的过程称为缩窄

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

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

类型 string 不是有效的判别式,但是类型 "hello world" 是因为,作为所有可能字符串的父类(super class)型,字符串类型的联合包括string 折叠为 string。例如,类型 string | “hello world” 正是 string 类型。

当我们在 TypeScript 中定义 constreadonly 属性时,编译器会将其类型推断为初始化文字的类型。

考虑:

const kind = "first";

这里kind的类型不是string而是"first"

同样,给定

class Kindred {
  readonly kind = "first";
}

kind 属性的类型不是string,而是"first"

在我们的代码中,判别式是一个名为 type 的属性,由并集的每个组成部分定义。

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

你有什么:

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) {}
}

你想要什么:

export class AddSuccessAction {
    readonly type = ADD_SUCCESS;

    constructor(public payload: Mission) {}
}

export class AddFailedAction {
    readonly type = ADD_FAILED;

    constructor(public payload: string) {}
}

一个工作的开关

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;
}

为什么这很重要:

字符串文字类型是一种常见且惯用的方式来区分联合的可能性,但是,通过将实现属性声明为 string 类型,该类型是所有字符串文字的父类(super class)型类型,我们抑制了类型推断,从而防止了缩小。请注意,string 不是可以缩小范围的类型。

一般来说,当一个值有一个初始化器时,最好利用 TypeScript 的类型推断。除了实现预期的场景之外,您还会对类型推断器捕获的错误数量印象深刻。我们不应该告诉编译器比它需要知道的更多,除非我们有意想要一个比它推断的更通用的类型。使用 --noImplicitAny,它总是让我们知道何时需要以类型注释的形式指定额外信息。

备注:

您可以通过将文字值指定为 constreadonly 属性的类型,从技术上指定类型并仍然保留缩小行为。然而,这增加了维护成本并且相当多余。

例如,以下是有效的:

export const ADD_FAILED: "[Mission] AddFailed" = "[Mission] AddFailed";

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

在他们的回答中,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") {

}

关于angular - reducer Action 中的 ngrx 有效负载未编译,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46264002/

相关文章:

html - 通过点击更改 <li> 样式

angular - SheetJs 自动转换日期 Angular 7

angular - 有条件地将 RouterLinkActive 属性停用为某个值

angular - NGRX 错误 "Dispatch expected an object, instead it received a function. If you' 重新使用 createAction 函数...”

angular - 如何在 ngrx 效果中使用 LatestFrom 进行单元测试

angular - ngrx 8 : Access state of @ngrx/data entity from reducer function

angular - 如何在 Angular 6 中同步加载组件?

html - 日期的默认值未显示

angular - ngOnChanges Hook 中 SimpleChanges 接口(interface)的类型检查

reactjs - 如何使用 typescript 为 HOC 编写通用类型?