我正在尝试将我的角度2应用程序迁移到角度4和ngrx 4。
我正在处理一个奇怪的打字稿编译问题,这是我在此次更新之前没有的。我在离线环境中工作,所以我不能在这里分享确切的代码。
我在线查找问题,我能找到的最接近的问题是这个问题: question about payload types 这个问题中的代码在某种程度上类似于我想要做的,但区别在于我从动作中删除了ActionTypes对象(这是该问题的建议答案),但它没有解决我的编译错误。
我不能把确切的代码加到这里,但我完全把我的代码基于ngrx示例 ngrx official example on github
让我们看一下书籍缩减器和书籍动作的例子 book actions book reducer
我的代码基本相同,当我尝试将有效负载从操作中取出时,每个reducer情况(在switch情况下)都会发生错误, 它告诉我类似的事情:
Type 'string | Book | Book[]' is not assignable to type 'Book'. Type 'string' is not assignable to type 'Book'.
(在我的代码中,而不是Book我有不同的类,但它几乎是相同的想法)
现在我通过从A |中转换动作类型来解决错误B |乙 作为与switch中的case相对应的特定动作类型(使用as typescript关键字)。 这个解决方案感觉很糟糕,我想知道是否有更好的解决方案。
我正在使用的版本: 打字稿:2.4.1(我怀疑它可能连接到ngrx示例,而不是使用与我相同的更新打字稿版本) ngrx @ store:4.0.3 ngrx @ core:1.2.0 角度:(最新的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
}
}
任务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 2.4有关0.1?不确定,需要帮助
答案 0 :(得分:9)
问题在于代码的行为及其类型注释是相互交叉的。
实际上,我会说代码被过度注释。
联盟类型和案例分析,它们通过基于类型推断和基于控制流的类型分析来实现工作,这是TypeScript最强大的两项功能。类型检查器消除联合可能性的过程称为缩小。
如代码所示,通过对称为判别式的属性执行值测试,可以将联合类型分解为其组成部分。
判别式是一种属性,其类型具有有限的可能值集,每个值通常对应于并集的情况。
string
不是有效的判别式,但"hello world"
是。string
。 (作为所有可能字符串的超类型,包括string
的字符串类型的并集折叠为const
)
当我们在TypeScript中定义readonly
或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
,一种类型是所有字符串文字类型的超类型,我们抑制类型推断,从而防止变窄。请注意,--noImplicitAny
不是可以缩小的类型。
通常,当值具有初始值设定项时,最好使用TypeScript的类型推断。除了启用预期方案之外,类型推理器将捕获的错误数量将给您留下深刻印象。我们不应该告诉编译器它需要知道的东西,除非我们故意想要一个比它推断的更通用的类型。使用const
时,我们总是会告诉我们何时需要以类型注释的形式指定额外信息。
<强>说明:强>
您可以在技术上指定类型,并通过将文字值指定为readonly
或export const ADD_FAILED: "[Mission] AddFailed" = "[Mission] AddFailed";
属性的类型来保留缩小行为。但是,这会增加维护成本,而且相当多余。
例如,以下内容有效:
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") {
}
但你只是在不必要地重复自己。
在他们的回答中,ilyabasiuk提供了一些很好的参考链接,特别是与ngrx一起使用文字类型,以及它是如何在最近的TypeScript语言迭代中进化的。
要理解为什么在不可变位置推断出文字类型,请考虑它能够实现强大的静态分析,从而更好地检测错误。
考虑:
ViewPager mViewPager = new ViewPager (this);
答案 1 :(得分:2)
以下是您正在尝试https://github.com/ngrx/example-app/pull/88#issuecomment-272623083
进行迁移的非常好的描述您必须进行下一次更改:
所以你会得到类似的东西:
/* 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;
Aluan Haddad在上面的帖子中提供了很好的解释。