网站搜索

javascript - Typescript 错误 TS2554:预期 0 个参数,但收到 1 个。在使用 React 的 useReducer 和 useContext 时出现


我有一些 useReducers 和 useContext,其中出现了类似下面列出的错误(TS2554)。我选出了 AuthReducer,因为它是最简单的。每次 Action dispatch 都会出现相同的错误。我尝试过查看其他解决方案并尝试它们,添加了一些更多的定义,但显然我没有按正确的方式去做!

这是我第一个使用 TypeScript 的项目,所以请手下留情!

据我所知,Dispatch 不期望任何参数,但却收到了一个参数。然而,当我声明我的 AuthReducer 时,它有一个类型为 Actions 的 action 参数,并返回 AuthState。

/AuthState.tsx:40:16 - error TS2554: Expected 0 arguments, but got 1.

40       dispatch({
41         type: USER_LOADED,
42         payload: res.data,
43       });

AuthReducer.tsx

import {
  USER_LOADED,
  AUTH_ERROR,
  LOGIN_SUCCESS,
  LOGIN_FAIL,
  LOGOUT,
  CLEAR_ERRORS,
} from '../types';
import { initialState } from './AuthState';

type Actions =
  | {
      type: 'USER_LOADED';
      payload: {
        name: string;
        id: string;
      };
    }
  | {
      type: 'AUTH_ERROR';
      payload?: string;
    }
  | {
      type: 'LOGIN_SUCCESS';
      payload: { token: string };
    }
  | {
      type: 'LOGIN_FAIL';
      payload: string;
    }
  | {
      type: 'LOGOUT';
      payload?: string;
    }
  | {
      type: 'CLEAR_ERRORS';
    };

interface AuthState {
  token?: string | null;
  isAuthenticated?: boolean;
  loading: boolean;
  user?: {
    name: string;
    id: string;
  } | null;
  error?: string | undefined | null;
}

const AuthReducer = (
  state: typeof initialState,
  action: Actions
): AuthState => {
  switch (action.type) {
    case USER_LOADED:
      return {
        ...state,
        isAuthenticated: true,
        loading: false,
        user: action.payload,
      };
    case LOGIN_SUCCESS:
      localStorage.setItem('token', action.payload.token);
      return {
        ...state,
        ...action.payload,
        isAuthenticated: true,
        loading: false,
      };
    case AUTH_ERROR:
    case LOGIN_FAIL:
    case LOGOUT:
      localStorage.removeItem('token');
      return {
        ...state,
        token: null,
        isAuthenticated: false,
        loading: false,
        user: null,
        error: action.payload,
      };
    case CLEAR_ERRORS:
      return {
        ...state,
        error: null,
      };
    default:
      return state;
  }
};

export default AuthReducer;

AuthContext.tsx

import { createContext } from 'react';
import { initialState } from './AuthState';

type authContextType = {
  loadUser: () => Promise<void> | null;
  login: (formData: {
    email: string;
    password: string;
  }) => Promise<void> | null;
  logout: () => void;
  clearErrors: () => void;

  error: string | null;
  isAuthenticated: boolean;
  loading: boolean;
  user: {
    name: string;
    id: string;
  };
  token: string;
};

const authContext = createContext<authContextType>(initialState); //TODO A more robust type is possible

export default authContext;

AuthState.tsx

import React, { useReducer } from 'react';
import axios from 'axios';
import AuthContext from './AuthContext';
import AuthReducer from './AuthReducer';
import setAuthToken from '../../utils/setAuthToken';
import {
  USER_LOADED,
  AUTH_ERROR,
  LOGIN_SUCCESS,
  LOGIN_FAIL,
  LOGOUT,
  CLEAR_ERRORS,
} from '../types';

export const initialState = {
  loadUser: null,
  login: null,
  logout: () => {},
  clearErrors: () => {},

  token: localStorage.getItem('token'),
  isAuthenticated: null,
  loading: true,
  error: null,
  user: null,
};

const AuthState: React.FC = ({ children }) => {
  const [state, dispatch] = useReducer(AuthReducer, initialState);

  //Load User
  const loadUser = async () => {
    if (localStorage.token) {
      setAuthToken(localStorage.token);
    }

    try {
      const res = await axios.get('api/auth');

      dispatch({
        type: USER_LOADED,
        payload: res.data,
      });
    } catch (err) {
      dispatch({ type: AUTH_ERROR });
    }
  };
  //Login User
  const login = async (formData: { email: string; password: string }) => {
    const config = {
      headers: {
        'Content-Type': 'application/json',
      },
    };
    try {
      const res = await axios.post('/api/auth', formData, config);

      dispatch({
        type: LOGIN_SUCCESS,
        payload: res.data,
      });

      loadUser();
    } catch (err) {
      dispatch({
        type: LOGIN_FAIL,
        payload: err.response.data.msg,
      });
    }
  };
  //Logout
  const logout = () => {
    dispatch({ type: LOGOUT });
  };
  //Clear Errors
  const clearErrors = () => {
    dispatch({ type: CLEAR_ERRORS });
  };

  return (
    <AuthContext.Provider
      value={{
        token: state.token,
        isAuthenticated: state.isAuthenticated,
        loading: state.loading,
        user: state.user,
        error: state.error,
        loadUser,
        login,
        logout,
        clearErrors,
      }}
    >
      {children}
    </AuthContext.Provider>
  );
};

export default AuthState;

答案#1

问题在于这里:

const AuthReducer = (
  state: typeof initialState,
  action: Actions
): AuthState ...

TypeScript 无法关联你的类型 typeof initialState 和 AuthState。但是 useReducer 的重载依赖于 reducer 的类型来推断结果的正确类型。

你在这里有几种重构选项。你可以直接告诉 TypeScript,它们是相同的类型:

const AuthReducer = (
  state: AuthState,
  action: Actions
): AuthState

或者使你的 initialState 类型更宽松一些:

const initialState: AuthState = {....}

问题的核心是类型 TypeofA 和 A:

const a = { a: null }
type TypeofA = typeof a

type A = { a: string | null }

并不完全相同。当仅将字段声明为 null 时,你会丢失 | string。要使它们相等,你可以编写带有类型断言的 a 对象:

const a = { a: null as string | null }

或者直接说明 a 的类型:

const a: A = { a: null }