代码之家  ›  专栏  ›  技术社区  ›  wmp224

如何编写严格要求Typescript中参数的函数类型

  •  4
  • wmp224  · 技术社区  · 9 年前

    我遇到了一个问题 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不是reducer(它是一个接受字符串并返回reducer的函数),但TypeScript对此没有问题。

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

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

    Reducer 键入“更严格”是否要求两个参数或以其他方式获得更大的信心,即ReducerMapObject实际上包含reducer函数?

    TypeScript playground 如果你想复制

    1 回复  |  直到 9 年前
        1
  •  4
  •   Shaun Luttin    9 年前

    好问题。。。对于这个相当长的答案,有两种可行的选择。你问了两个问题,我分别回答。

    问题1:

    有没有办法使减速器类型更严格地要求这两个参数。。。

    障碍1:丢弃功能参数

    is documented here 在“比较两个函数”标题下它说“我们允许‘丢弃’参数。”也就是说,参数较少的函数可分配给参数较多的函数。这个 rationale is in the FAQ . 简而言之,以下赋值是安全的,因为参数较少的函数“可以安全地忽略额外的参数”

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

    障碍2:函数参数双变量

    第二个障碍是 function parameters are bivariant . 那个 不能 Pair 以及接受

    class Pair {
        x: number;
        y: number;
    }
    
    let addPair: (p: Pair) => number;
    

    一对 .

    substitutability rules ,但函数遵循双变量规则。在其简单的类型分配中,TypeScript允许我们分配类型 到类型 Single 但不指定类型 到类型 一对 . 这是预期的替代品。

    class Single {
        x: number;
    }
    
    let s: Single = new Pair(); // works
    let p: Pair = new Single(); // fails because of a missing property.
    

    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!
    

    结果是,一个函数期望 将接受 仅有一个的

    影响 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 接受a 减速器 参数更少是安全的。编译器仍将确保:

    1. 包括 这个 减速器 参数,以及
    2. 每个实现一个 仅对其(可能较短)参数列表进行操作。

    ...或者用另一种方法来更确信ReducerMapObject实际上包含reducer函数?

    reducerCreator 函数(和其他形状异常的函数)与 Reducer<S>

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

    ReducerArgs<S> ,将给我们 更多 还原干燥器

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

    然后用 a union type

    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相同的类型”)

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