我遇到了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对此没有问题。
看来问题的根源是还原器本质上归结为any => any
,因为functions with fewer parameters are assignable to functions that take more params。
这意味着ReducersMapObject
类型基本上就是{[key: string]: function}
有没有办法使Reducer
类型更严格地要求两个参数,或者有没有其他方法来更好地确保ReducersMapObject实际上包含reducer函数?
如果您尝试复制,这些代码都在TypeScript playground中编译
1条答案
按热度按时间2w2cym1i1#
问得好...在这个相当长的答案的结尾有两个可行的选择。你问了两个问题,我分别回答了每个。
问题1:
有没有一种方法可以使Reducer类型更严格地要求两个参数...
要做到这一点是很困难的,因为TypeScript函数中存在两个障碍。
障碍物1:丢弃函数参数
一个障碍,你已经注意到了,is documented here在标题“比较两个函数”下。它说“我们允许'丢弃'参数。”也就是说,参数较少的函数可以赋值给参数较多的函数。基本原理在FAQ中。简而言之,下面的赋值是安全的,因为参数较少的函数“可以安全地忽略额外的参数。”
障碍2:函数参数双方差
第二个障碍是函数参数是双变量的,这意味着我们不能用用户定义的类型参数来解决这个问题,在某些语言中,我们可以定义
Pair
沿着一个接受Pair
的函数。在具有协变函数的语言中,上述方法会将参数限制为
Pair
的子类型。在TypeScript中,简单类型赋值遵循预期的可替换性规则,但函数遵循二元规则。在其简单类型赋值中,TypeScript允许我们将类型
Pair
赋值给类型Single
,但不允许将类型Single
赋值给类型Pair
。这是预期的替换。然而,TypeScripts函数是二元的,并且不受相同的限制。
结果是期望
Pair
的函数将接受Single
。对
Reducers
的影响以下两种技术都不会强制
Reducer
实现必须接受的参数(或类属性)的数量。这实际上可能不是问题,因为让
ReducersMapObject
接受一个参数较少的Reducer
是安全的。编译器仍然会确保:1.对
Reducer
的每个调用包括 * 所有 *Reducer
参数,并且问题2:
...还是另一种方法来更好地确保ReducersMapObject实际包含reducer函数?
我们尝试做的一件事是使
reducerCreator
函数(以及其他不常见的函数)与Reducer<S>
函数类型不兼容。可行选项1:自定义参数类型
上面的第二种技术,使用一个名为
ReducerArgs<S>
的用户定义类型,会给予我们带来更多的信心,但它不会提供完全的信心,因为我们仍然会有双方差,但它会确保编译器拒绝reducerCreator
。可行选项2:泛型和联合类型
另一种选择是使用如下所示的通用
ReducerMapObject<T>
:然后用a union type将其参数化,a union type列出了所有reducer的类型。
结果将是
any => any
变成T => T
,其中T
是联合体中列出的类型之一(顺便说一句,如果有一个类型说“x可以是任何类型,只要它是与y相同的类型”,那就太好了)。虽然以上两种方法都需要更多的代码,而且有点笨拙,但它们确实满足了您的需求。这是一个有趣的研究项目。谢谢您的提问!