“ --strictFunctionTypes”和通用联合类型的推断

时间:2018-11-07 10:09:46

标签: typescript

我的项目遇到了一些我完全不清楚的问题

// =========================== RxJS types imitation ===========================================================

interface Subscription {
    unsubscribe(): void
}

type Subscribe<T> = (value: T) => void

interface Observable<T> {
    subscribe(subscribe: Subscribe<T>): Subscription
}

interface Subject<T> {
    next(value: T): void
    subscribe(subscribe: Subscribe<T>): Subscription
}


// =========================== My custom types ===========================================================


export interface Reducer<S, A> {
  (state: S, action: A): S;
}

export interface Effect<A, S> {
  (action$: Subject<A>, state$: Observable<S>): Subscription;
}

export interface OptionalParams<S, A> {
  effects?: Effect<A, S>[];
}


// =========================== Reproducing concrete types and variables ===========================================================

const initialState = {
  some: true
}

type State = typeof initialState

interface BaseAction<T extends string> {
  type: T
}

interface ActionA extends BaseAction<"A"> {
  payload: string
}

interface ActionB extends BaseAction<"B"> {
  payload: number
}

interface ActionC extends BaseAction<"C"> {
  payload: boolean
}

declare const reducer: Reducer<State, ActionA | ActionB | ActionC>

declare const outerAction$: Observable<ActionA | ActionB>

declare function createState$<S, A>(
  reducer: Reducer<S, A>,
  initialState: S,
  outerAction$: Observable<A>,
  optionalParams?: OptionalParams<S, A>,
): Observable<S>


// =========================== Reproducing the problem ===========================================================

const state$1 = createState$(reducer, initialState, outerAction$)

declare const someEffect: Effect<ActionA | ActionB | ActionC, State>

const state$2 = createState$(reducer, initialState, outerAction$, {
  effects: [
    someEffect,
  ]
})

the same code in typescript playground (please switch on/off strictFunctionTypes option and hover createState$ function in the last section)

如果启用了--sctrictFunctionTypes选项,则在上一节中悬停createState$函数将显示推断出的一般A不是ActionA | ActionB | ActionC类型,而是ActionA | ActionB类型。我不了解--sctrictFunctionTypes如何影响联合推理。我以为打字稿会尝试推断最常见的联合类型。这些知识对我来说是必不可少的,因为对于定义为嵌入式箭头函数的效果,我需要将action$参数的类型设为Subject<ActionA | ActionB | ActionC>。不仅如此:如果在someEffect下添加箭头功能效果,我的项目中的打字稿(v3.1 +)会显示一个错误,提示Types of property 'effects' are incompatible

谢谢。

1 个答案:

答案 0 :(得分:1)

您可能知道,TypeScript通过将参数类型与参数类型进行匹配并为每个类型参数生成一组“推论”来推断函数调用的类型参数的类型参数。每个推论是“协变”还是“逆变”,这取决于推论的类型是否与协变位置或协变位置中类型参数的出现相匹配。然后有规则根据协变和反推论的集合确定类型参数的最终推断类型。 strictFunctionTypes会影响某些位置是协变还是逆变,从而改变最终结果。我还没有弄清您的示例中发生的情况的细节;如果您好奇,请查看TypeScript checker中的inferTypesgetInferredType函数。

我了解您的实际问题是,您正在(a, s) => { ... }数组中编写内联箭头函数effects,并且希望a获得上下文类型Subject<ActionA | ActionB | ActionC>因此您可以使用a.next来调用ActionC。我可以想到几种实现此目的的方法:

  1. 通过使用虚拟交集,将A参数类型的outerAction$createState$的出现的“推理优先级”降低:

    declare function createState$<S, A>(
      reducer: Reducer<S, A>,
      initialState: S,
      outerAction$: Observable<A & {dummy?: undefined}>,
      optionalParams?: OptionalParams<S, A>,
    ): Observable<S>
    

    然后,来自A的{​​{1}}的推断将比来自reducer的推断优先。但是,更改outerAction$的类型可能会产生不良后果。

  2. outerAction$添加类型为A的伪属性,以便Reducer参数为您提供协变推断,而不仅仅是协变推断:

    reducer

    但是,这可能会对export interface Reducer<S, A> { (state: S, action: A): S; _dummy?: A; } 界面的其他使用产生不良影响。

  3. 在对Reducer的调用上传递显式类型参数。
  4. createState$分解为一个使用减速器的咖喱函数,然后返回另一个使用其余参数的函数。然后,createState$S的类型参数将在第一个函数调用期间基于化简器进行固定,并且不受A的影响。