useReducer & Typescript
useReducer与Typescript的类型计算。
例子
从一段简单的代码开始。
import React, { useReducer } from "react";
type State = {
count: number;
};
type Action = {
type: "increase" | "decrease";
};
export default function Demo() {
const initialState: State = {
count: 0
};
const reducer = (prev: State, action: Action) => {
switch (action.type) {
case "increase":
return { count: prev.count + 1 };
}
};
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
<button onClick={() => dispatch({ type: "increase" })}>+</button>
<button onClick={() => dispatch({ type: "decrease" })}>-</button>
<span>{state.count}</span>
</>
);
}
如果简单复制粘贴代码进vscode,可以看到在useReducer 的initialState 位置显示如下错误。
Argument of type 'State' is not assignable to parameter of type 'never'.ts(2345)
const initialState: State
但是为什么useReducer的第二个参数类型会是never呢?
检查react的index.d.ts可以看到如下定义
function useReducer<R extends Reducer<any, any>, I(
reducer: R,
initializerArg: I & ReducerState<R>,
initializer: (arg: I & ReducerState<R>) =>ReducerState<R>
): [ReducerState<R>, Dispatch<ReducerAction<R>>;
type ReducerState<R extends Reducer<any, any>> = R extends Reducer<infer S, any> ? S : never;
type Reducer<S, A> = (prevState: S, action: A) => S;
显而易见此时的reducer 缺少了对{type:"decrease"}的处理。如果hover到reducer上,可以看到这个函数实际的类型:
const reducer: (prev: State, action: Action) => {
count: number;
} | undefined
从中可以得出,例子中useReducer传入的reducer传回的类型从根本上不满足Reducer类型的定义(传回的类型不满足State类型定义),因而导致ReducerState<R>的值为never。
进而,initializerArg: I & ReducerState<R>的类型为never,所以出现了此时的报错。
改进
其实说不定聪明的你已经注意到了,应该在reducer定义的位置显示表示传回参数类型,这样在不满足时能够显示正确的错误。
代码如下
const reducer = (prev: State, action: Action): State => {
//...not always returning State type
};
可以看到正确的报错
Function lacks ending return statement and return type does not include 'undefined'.
完善reducer:
const reducer = (prev: State, action: Action): State => {
switch (action.type) {
case "increase":
return { count: prev.count + 1 };
case "decrease":
return { count: prev.count - 1 };
}
};
就能正常编译运行了。
之前阅览的某些教程为了取消报错,修改prev: State为prev: any,同样不显示标记返回类型,个人认为不是好的做法。