【问题标题】:How to use this.props.action function in TypeScript?如何在 TypeScript 中使用 this.props.action 函数?
【发布时间】:2021-08-28 15:06:11
【问题描述】:

我的 authActions.tsx 文件如下所示:

import {
    LOGIN_REQUEST,
    LOGIN_SUCCESS,
    LOGIN_FAILURE
} from './types';

import axios from 'axios';
import { Dispatch } from 'react';

interface ILoginObject {
    email: string,
    password: string
}

interface ILoginDispatch {
    type: string,
    payload?: {
        success? : boolean,
        status?: number,
        message?: string,
        user?: {
            id: string,
            email: string
        },
        access_token?: string,
        expires_in?: string
    }
}

export const userLogin = (loginObject: ILoginObject) => async (dispatch: Dispatch<ILoginDispatch>): Promise<void> => {
    dispatch({
        type: LOGIN_REQUEST
    });

    const response = await axios.post(`/api/login`, loginObject);

    if(response.data && response.data.access_token) {
        dispatch({
            type: LOGIN_SUCCESS,
            payload: response.data
        });
    }
    else {
        dispatch({
            type: LOGIN_FAILURE,
            payload: response.data
        });
    }
}

我的 Login.tsx 文件如下所示:

import React from 'react';
import { connect } from 'react-redux';

import { userLogin } from '../store/actions/authActions';

interface IProps {
    auth: {
        payload?: {
            success? : boolean,
            status?: number,
            message?: string,
            user?: {
                id: string,
                email: string
            },
            access_token?: string,
            expires_in?: string
        }
    }
}

interface IState {
    email: string,
    password: string
}

interface IEvent {
    target: HTMLInputElement
}

class Login extends React.Component<IProps, IState> {

    state: IState = {
        email: "",
        password: ""
    }

    onEmailChange = (e: IEvent): void => {
        this.setState({
            email: e.target.value
        })
    }

    onPasswordChange = (e: IEvent): void => {
        this.setState({
            password: e.target.value
        })
    }

    onSubmit = (): void => {
        //
    }

    render(): JSX.Element {
        return(
            <React.Fragment>
                <div>
                    <div>
                        Email
                    </div>
                    <div>
                        <input type="text" name="email" onChange={e => this.onEmailChange(e)} />
                    </div>
                </div>
                <div>
                    <div>
                        Password
                    </div>
                    <div>
                        <input type="password" name="password" onChange={e => this.onPasswordChange(e)} />
                    </div>
                </div>
                <div>
                    <button onClick={this.onSubmit}>Submit</button>
                </div>
            </React.Fragment>
        )
    }
}

const mapStateToProps = (state: IProps) => {
    return {
        auth: state.auth
    };
}

export default connect(mapStateToProps, { userLogin })(Login);

现在,我想使用“onSubmit = () => {}”函数中的“登录”操作发布请求。在 JavaScript 中,这可以通过简单的编写来完成

return this.props.userLogin({email: this.state.email, password: this.state.password}).then(() => {
    // Something
})

但由于是 TypeScript,所以上面的代码不起作用,需要提前定义“userLogin”函数。我尝试将“userLogin: () => void”添加到“IProps”界面,但我收到错误消息“属性 'then' 在类型 'void' 上不存在”以使用该函数并且代码不起作用.

谁能帮忙?

【问题讨论】:

  • 帮自己一个忙,切换到功能组件。当然,这可以正确输入,但使用 useDispatch 钩子会更容易。

标签: javascript reactjs typescript react-redux


【解决方案1】:

您的设置不合逻辑,因为您使用类型 IProps 作为两者的类型:

  • Login 组件的 props
  • Redux 存储的根状态作为参数传递给mapStateToProps

您希望您的组件采用 userLogin 属性,即 function。此函数不是 Redux 存储状态的属性,因此 Redux 状态和组件 props 不可能共享相同的类型。

如果您修复 IProps 以准确表示组件道具,那么您将遇到 mapStateToProps 和 react-redux connect HOC 的问题,因为它们正在使用 (state: IProps)。您已经在回复@Kakiz 的回答中发现了这一点。


您不需要在此组件的文件中确定您的 redux 状态类型,因为该类型是全局的。你从 store 变量中推断出 RootState 类型的 react-redux 文档 recommend,如下所示:

export type RootState = ReturnType<typeof store.getState>

您将该行放在创建商店的文件中,将RootState 类型导入您需要的任何位置,例如mapStateToProps

他们关于 TypeScript 使用的出色指南包括关于 Typing the connect higher order component 的整个部分,尽管他们建议使用函数组件和钩子,因为它们更容易正确键入。


不要害怕export 类型并重用它们。您的操作文件中定义的ILoginObject 与您的组件中定义的IState 相同。你的IPropsILoginDispatch 也有很多重复。

我经常发现内联编写简单的事件处理程序更容易,因为这样您就无需键入 e 参数。


您需要 TypeScript 类型知道您安装了 redux-thunk 中间件,以便它知道调度操作将返回 Promise,因为这不是默认行为。

userLogin thunk 本身可以使用 Redux 官方工具包中的 createAsyncThunk 函数进行简化。这也将处理 axios.post 请求本身中的错误,您当前没有发现这些错误。它也是 RTK Query 的一个很好的候选者,它刚刚集成到 Redux Toolkit 中。


这里有一个更好的组件设置,它是完全类型安全的,尽管任何地方都没有类型。一切都是推断出来的。

import React, { useState } from "react";
import { unwrapResult } from "@reduxjs/toolkit";
import { userLogin } from "../store/slice";
import { useSelector, useDispatch } from "../store";

// no props needed anymore
export default function Login() {
  // not sure where you actually use this
  const auth = useSelector((state) => state.auth);

  const dispatch = useDispatch();

  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const onSubmit = () => {
    dispatch(userLogin({ email, password }))
      .then(unwrapResult) // explained here: https://redux-toolkit.js.org/api/createAsyncThunk#unwrapping-result-actions
      .then((data) => alert(`logged in user ${data.user}`))
      .catch((error) => alert(`login error: ${error.message}`));
  };

  return (
    <React.Fragment>
      <div>
        <label>
          Email
          <input
            type="text"
            name="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
          />
        </label>
      </div>
      <div>
        <label>
          Password
          <input
            type="password"
            name="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
          />
        </label>
      </div>
      <div>
        <button onClick={onSubmit}>Submit</button>
      </div>
    </React.Fragment>
  );
}

这是基于以下商店的设置:

import { AnyAction, configureStore } from "@reduxjs/toolkit";
import {
  TypedUseSelectorHook,
  useDispatch as _useDispatch,
  useSelector as _useSelector
} from "react-redux";
import { logger } from "./middleware";
import auth from "./slice";

const store = configureStore({
  reducer: {
    auth
  }
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

export type Action = AnyAction;

export const useDispatch: () => AppDispatch = _useDispatch;

export const useSelector: TypedUseSelectorHook<RootState> = _useSelector;
export default store;

还有减速器/动作:

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";

export interface AuthData {
  success?: boolean;
  status?: number;
  message?: string;
  user?: {
    id: string;
    email: string;
  };
  access_token?: string;
  expires_in?: string;
}

export interface ILoginObject {
  email: string;
  password: string;
}

export const userLogin = createAsyncThunk(
  "auth/Login",
  async (loginObject: ILoginObject, { rejectWithValue }) => {
    const response = await axios.post<AuthData>(`/api/login`, loginObject);

    if (response.data && response.data.access_token) {
      // returned value is the success payload
      return response.data;
    } else {
      // alternatively, can throw an Error
      return rejectWithValue(response.data);
    }
  }
);

const initialState: AuthData = { success: false };

const authSlice = createSlice({
  initialState,
  name: "auth",
  // reducers creates both actions and reducer cases
  reducers: {
    logout: (state, action) => {
      // clears the state and replaces with the initial state
      return initialState;
    }
  },
  // extraReducers adds reducer cases for existing actions
  extraReducers: (builder) =>
    builder
      .addCase(userLogin.fulfilled, (state, action) => {
        // TODO - this replaces the entire state, probably not what you actually want
        return action.payload;
      })
      .addCase(userLogin.rejected, (state, action) => {
        // TODO
      })
      .addCase(userLogin.pending, (state, action) => {
        // TODO - what is the correct number?
        state.status = 0;
      })
});

export const { logout } = authSlice.actions;
export default authSlice.reducer;

您需要在 reducer 中填写一些空白。

Code Sandbox Link

【讨论】:

  • 非常感谢。这是一个巨大的帮助。但我仍然对“import auth from './slice';”这行感到困惑。和 const store = configureStore({ reducer: { auth } });那我应该在“auth”中保留什么?
  • ‘./slice’ 就是我所说的文件,它具有导出默认值,其中包含减速器。查看 CodeSandbox 的链接,它有完整的设置?codesandbox.io/s/redux-typescript-user-login-o1yy5
【解决方案2】:

由于您正在调用this.props.login(...).then(...),我认为login 应该返回一个Promise。您只需要在IProps 界面中输入它。此外,它看起来应该接受一个包含 emailpassword 的参数,所以你也应该输入它。

interface IProps {
  login: (credentials: { email: string; password: string; }) => Promise<void>;
}

【讨论】:

  • 因为我的函数是“userLogin”,所以我尝试了这个:userLogin: ({ email: string; password: string; }) => Promise。我有一些错误。第一个是箭头。它说, ” ';'预期的 ”。第二个是承诺。它说,“'Promise' 缺少返回类型注释,隐含地具有 'any' 返回类型。”
  • 忘记提供参数名称,更新了我的答案。
【解决方案3】:

如果函数返回 void,则意味着您没有返回任何内容,您无法获取属性 then。你需要返回一个Promise

interface IProps {
    auth: {
        payload?: {
            success? : boolean,
            status?: number,
            message?: string,
            user?: {
                id: string,
                email: string
            },
            access_token?: string,
            expires_in?: string
        }
    };
    login: () => Promise<void>;
}

【讨论】:

  • 你能告诉我如何以及在哪里这样做吗?
  • 当我使用“return this.props.login().then(() => { // Some code })”时,我得到这个错误:Type 'Promise' is not可分配给类型 'void'
  • 您是否需要返回this.props.login?考虑到 onSubmit 的返回类型是 void,你不应该返回任何东西
  • 我的函数是“userLogin()”。所以,我使用了“userLogin: () => Promise;”。但我收到此错误:“通用类型 'Promise' 需要 1 个类型参数”。
  • 这些错误是因为您使用IProps 作为 Redux 存储状态的类型。我会写另一个答案来解释它。
猜你喜欢
  • 1970-01-01
  • 2012-11-04
  • 1970-01-01
  • 2015-10-31
  • 2021-10-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-11
相关资源
最近更新 更多