我遇到问题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中编译
答案 0 :(得分:4)
好问题......在这个相当长的答案结束时有两个可行的选择。你问了两个问题,我分别回答了。
有没有办法让Reducer类型更严格,需要两个参数......
由于TypeScript函数存在两个障碍,因此很难实现这一点。
您已经注意到的一个障碍,is documented here在“比较两个功能”标题下。它说“我们允许'丢弃'参数。”也就是说,具有较少参数的函数可分配给具有更多参数的函数。 rationale is in the FAQ。function parameters are bivariant。简而言之,以下赋值是安全的,因为具有较少参数的函数“可以安全地忽略额外的参数。”
const add: (x: number, y: number) =
(x: number) => { return x; }; // works and is safe
第二个障碍是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
是安全的。编译器仍将确保:
Reducer
的每次调用都包含所有 Reducer
个参数,Reducer
的每个实现仅对其(可能很短的)参数列表进行操作。...或其他方式让ReducersMapObject实际上包含reducer函数更有信心?
我们要做的一件事是使reducerCreator
函数(以及其他异常形状的函数)与Reducer<S>
函数类型不兼容。这是两个可行的选择。
上面的第二种技术,使用名为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!
}
另一种选择是使用这样的通用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的类型相同。”)
虽然上述两个内容涉及更多代码并且有点笨拙,但它们确实符合您的目的。这是一个有趣的研究项目。谢谢你的提问!