如何在 Typescript 中编写严格要求其参数的 Function 类型

wmp*_*224 4 typescript redux 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
}
Run Code Online (Sandbox Code Playgroud)

我希望reducerMap抛出一个错误,因为reducerCreator不是一个reducer(它是一个接受字符串并返回reducer的函数),但TypeScript对此没问题。

问题的根源似乎在于,Reducer 本质上可以归结为any => any因为参数较少的函数可以分配给需要更多参数的函数

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

有没有一种方法可以使Reducer类型对需要两个参数更加严格,或者有其他方法可以让人们更加确信ReducersMapObject实际上包含reducer函数?

如果您尝试复制,此代码将在TypeScript 游乐场中全部编译

Sha*_*tin 5

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

问题一:

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

由于 TypeScript 函数中的两个障碍,实现这一点将很困难。

障碍一:丢弃函数参数

您已经注意到的一个障碍已记录在“比较两个函数”标题下。它说“我们允许‘丢弃’参数。” 也就是说,参数较少的函数可以分配给参数较多的函数。基本原理在常见问题解答中。简而言之,下面的赋值是安全的,因为参数较少的函数“可以安全地忽略额外的参数”。

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

障碍2:函数参数双方差

第二个障碍是函数参数是双变量的。这意味着我们无法使用用户定义的类型参数来解决这个问题。在某些语言中,我们可以定义Pair一个接受Pair.

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

let addPair: (p: Pair) => number;
Run Code Online (Sandbox Code Playgroud)

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

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

class Single {
    x: number;
}

let s: Single = new Pair(); // works
let p: Pair = new Single(); // fails because of a missing property.
Run Code Online (Sandbox Code Playgroud)

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

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!
Run Code Online (Sandbox Code Playgroud)

结果是期望 a 的函数Pair将接受 a 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
Run Code Online (Sandbox Code Playgroud)

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

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

问题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!
}
Run Code Online (Sandbox Code Playgroud)

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

另一种选择是使用ReducerMapObject<T>像这样的泛型:

interface ReducersMapObject<T> {
  [key: string]: Reducer<T>;
}
Run Code Online (Sandbox Code Playgroud)

然后使用列出所有减速器类型的联合类型对其进行参数化。

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!
}
Run Code Online (Sandbox Code Playgroud)

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

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