reducer 动作中的 ngrx 有效负载未编译

ngrx payload in reducer action not compiling

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

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

我在网上找了这个问题,我能找到的最接近的是这个问题: 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 | 转换操作类型来解决错误乙 |乙 成为与开关中的案例对应的特定操作类型(使用 as typescript 关键字)。 这个解决方案感觉很烂,很hacky,想知道有没有更好的。

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


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

任务-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
    }
}

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

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

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

其实我会说代码注释过多

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

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

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

类型 string 不是有效的判别式,但类型 "hello world" 是因为,作为所有可能字符串的超类型,包括 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

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 类型,该类型是所有字符串文字类型的超类型,我们抑制了类型推断,从而防止了缩小。请注意,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") {

}

这里对迁移的描述非常好,您正在尝试这样做https://github.com/ngrx/example-app/pull/88#issuecomment-272623083

您必须进行下一步更改:

  • 删除操作常量字符串的类型定义;
  • 删除类型 属性 的类型定义;
  • add /* tslint:disable:typedef*/ 如果你的 tslint 配置为不允许丢失类型定义(是的,它看起来 不是很好,但如果我必须在类型的强规则之间做出选择 动作文件中的定义 reducer 中的支持类型 我会 绝对选择第二个);

所以你会得到类似的东西:

/* tslint:disable:typedef*/
export const ADD_SUCCESS = "[Mission] AddSuccess";
export const ADD_FAILED = "[Mission] AddFailed";

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

   constructor(public payload: Mission) {}
}

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

   constructor(public payload: string) {}
}

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

...

export type Actions =
    AddSuccessAction |
    AddFailedAction;

A​​luan Haddad 在上面的 post 中提供了很好的解释为什么它如此有效。