#javascript #reactjs #typescript #use-context #use-reducer
Вопрос:
У меня есть несколько обучающих программ и useContext, в которых я получаю ошибки, аналогичные приведенным ниже (TS2554). Я выбрал authReducer, так как он самый простой. Я получаю одну и ту же ошибку при каждой отправке действия. Я поиграл, рассматривая другие решения и пробуя их, Добавил еще несколько определений, но ясно, что я делаю не то, что должен делать!
Это мой первый проект, над которым я работаю с машинописным текстом, так что полегче со мной!
насколько я могу судить, Диспетчерская не ожидает никаких аргументов, но получает их. Однако, когда я объявляю свой authReducer, он имеет аргумент действия типа Действия и возвращает состояние аутентификации.
/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.
state: typeof initialState,
хотя эта линия выглядит неправильно, не так лиstate: typeof AuthState,
?2. Эй, Том, спасибо за ответ. Я попробовал и поиграл еще немного, но, к сожалению, это, похоже, не повлияло на данную ошибку
3. Я больше не вижу ничего странного -> не могли бы вы сказать мне, что >
USER_LOADED
находится в../types
папке
Ответ №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 }
это не совсем одно и то же. Вы теряете | string
при объявлении поля просто как null
. Чтобы сделать их равными, вы можете либо написать a
объект с утверждениями типа:
const a = { a: null as string | null }
или прямо указать тип a
:
const a: A = { a: null }