useReducer与Typescript的类型计算。
例子
从一段简单的代码开始。
tsximport 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可以看到如下定义
tsfunction 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上,可以看到这个函数实际的类型:
tsconst reducer: (prev: State, action: Action) => {count: number;} | undefined
从中可以得出,例子中useReducer传入的reducer传回的类型从根本上不满足Reducer类型的定义(传回的类型不满足State类型定义),因而导致ReducerState<R>的值为never。
进而,initializerArg: I & ReducerState<R>的类型为never,所以出现了此时的报错。
改进
其实说不定聪明的你已经注意到了,应该在reducer定义的位置显示表示传回参数类型,这样在不满足时能够显示正确的错误。
代码如下
tsconst reducer = (prev: State, action: Action): State => {//...not always returning State type};
可以看到正确的报错
Function lacks ending return statement and return type does not include 'undefined'.
完善reducer:
tsconst 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,同样不显示标记返回类型,个人认为不是好的做法。