如何在Typescript

时间:2017-08-26 19:38:29

标签: typescript redux typescript-typings typescript2.4

我遇到问题combineReducers不够严格而且我不确定如何绕过它:

interface Action {
  type: any;
}

type Reducer<S> = (state: S, action: Action) => S;

const reducer: Reducer<string> = (state: string, action: Action) => state;

const reducerCreator = (n: number): Reducer<string> => reducer;

interface ReducersMapObject {
  [key: string]: Reducer<any>;
}

const reducerMap: ReducersMapObject = {
  test: reducer,
  test2: reducerCreator
}

我希望reducerMap抛出错误,因为reducerCreator不是减速器(它是一个接受字符串并返回减速器的函数),但是TypeScript很好用。

问题的根源似乎是Reducer基本归结为any => any,因为functions with fewer parameters are assignable to functions that take more params ..

这意味着ReducersMapObject类型基本上只是{[key: string]: function}

有没有办法让Reducer类型更严格地要求两个参数或其他方式来更加确信ReducersMapObject实际上包含reducer函数?

如果您尝试复制,则此代码全部在TypeScript playground中编译

1 个答案:

答案 0 :(得分:4)

好问题......在这个相当长的答案结束时有两个可行的选择。你问了两个问题,我分别回答了。

问题1:

  

有没有办法让Reducer类型更严格,需要两个参数......

由于TypeScript函数存在两个障碍,因此很难实现这一点。

障碍1:丢弃功能参数

您已经注意到的一个障碍,is documented here在“比较两个功能”标题下。它说“我们允许'丢弃'参数。”也就是说,具有较少参数的函数可分配给具有更多参数的函数。 rationale is in the FAQfunction parameters are bivariant。简而言之,以下赋值是安全的,因为具有较少参数的函数“可以安全地忽略额外的参数。”

const add: (x: number, y: number) = 
           (x: number) => { return x; }; // works and is safe

障碍物2:功能参数Bivariance

第二个障碍是substitutability rules。那  意味着我们无法使用用户定义的类型参数解决此问题。在某些语言中,我们可以定义Pair以及接受Pair的函数。

class Pair {
    x: number;
    y: number;
}

let addPair: (p: Pair) => number;

在具有协变函数的语言中,上述内容会限制Pair的子类型的参数。

在TypeScript中,简单类型赋值遵循预期的a union type,但函数遵循双变量规则。在简单的类型分配中,TypeScript允许我们将类型Pair分配给Single类型,但不能将类型Single分配给类型Pair。这是一种预期的替代品。

class Single {
    x: number;
}

let s: Single = new Pair(); // works
let p: Pair = new Single(); // fails because of a missing property.
但是,

TypeScripts函数是双变量的,并且不受相同的限制。

let addSingle: (s: Single) => number; 
addSingle = (p: Pair) => p.x + p.y; // as expected, Pair is assignable to Single.

let addPair: (p: Pair) => number;
addPair = (s: Single) => s.x; // surprise, Single is assignable to Pair!

结果是期望Pair的函数将接受Single

Reducers

的影响

以下两种技术都不会强制Reducer实现必须接受的参数(或类属性)的数量。

class Action { }

// no restriction - TypeScript allows discarding function parameters 
type Reducer01<S> = (state: S, action: Action) => S;
const reducer01: Reducer01<number> = (state: number) => 0; // works

// no restriction - TypeScript functions have parameter bivariance
class ReducerArgs<S> { 
    state: S;
    action: Action;
}
type Reducer02<S> = (args: ReducerArgs<S>) => S;
const reducer02 = (args: { state: number }) => 0; // works

实际上这可能不是问题,因为让ReducersMapObject接受具有较少参数的Reducer是安全的。编译器仍将确保:

  1. Reducer的每次调用都包含所有 Reducer个参数,
  2. Reducer的每个实现仅对其(可能很短的)参数列表进行操作。
  3. 问题2:

      

    ...或其他方式让ReducersMapObject实际上包含reducer函数更有信心?

    我们要做的一件事是使reducerCreator函数(以及其他异常形状的函数)与Reducer<S>函数类型不兼容。这是两个可行的选择。

    可行选项1:用户定义的参数类型

    上面的第二种技术,使用名为ReducerArgs<S>的用户定义类型,将为我们提供更多信心。它不会提供完全的信心,因为我们仍然会有双变量,但它会确保编译器拒绝reducerCreator。以下是它的外观:

    interface Action {
      type: any;
    }
    
    // define an interface as the parameter for a Reducer<S> function
    interface ReducerArgs<S> { 
        state: S;
        action: Action
    }
    
    type Reducer<S> = (args: ReducerArgs<S>) => S;
    
    const reducer: Reducer<string> = (args: ReducerArgs<string>) => args.state;
    
    const reducerCreator = (n: number): Reducer<string> => reducer;
    
    interface ReducersMapObject {
      [key: string]: Reducer<any>;
    }
    
    const reducerMap: ReducersMapObject = {
      test: reducer,
      test2: reducerCreator // error!
    }
    

    可行选项2:泛型和联盟类型

    另一种选择是使用这样的通用ReducerMapObject<T>

    interface ReducersMapObject<T> {
      [key: string]: Reducer<T>;
    }
    

    然后使用https://developer.android.com/reference/android/content/Intent.html对其进行参数化,列出所有Reducer的类型。

    const reducer: Reducer<string> = (state: string, action: Action) => state;
    const reducer1: Reducer<number> = (state: number, action: Action) => state;
    
    const reducerMap: ReducersMapObject<string | number> = {
        test: reducer,
        test1: reducer1,
        test2: reducerCreator // error!
    }
    

    结果将any => any变为T => T,其中T是联合中列出的类型之一。 (顺便说一句,有一个类型可以说“x可以是任何类型,只要它与y的类型相同。”)

    虽然上述两个内容涉及更多代码并且有点笨拙,但它们确实符合您的目的。这是一个有趣的研究项目。谢谢你的提问!