【问题标题】:Trying to Call Multiple ActionCreators in Component using React Redux and Typescript尝试使用 React Redux 和 Typescript 在组件中调用多个 ActionCreators
【发布时间】:2021-05-29 11:43:54
【问题描述】:

我有两个组件,用户和 AppSettings。我正在尝试在 App 组件中访问他们的商店。通过阅读 react redux 帮助文档,我尝试了几种不同的方法,但我无法弄清楚如何让我的 ma​​pDispatchToProps 函数在 App 中工作。我得到的最接近的是下面的代码,它抛出错误 TypeError: this.props.requestUser is not a function

有谁知道我应该如何构建我的 ma​​pDispatchToProps 以从两个商店正确导入我的 actionCreators?还是我有其他问题并且在错误的位置打猎?非常感谢任何帮助,谢谢。

错误截图

  33 | }
  34 | 
  35 | private fetchUser() {
> 36 |     this.props.requestUser();
     | ^  37 | }
  38 | 
  39 | public render() {

AppSettings.ts

import { Action, Reducer } from 'redux';
import { AppThunkAction } from './';

// app settings state
export interface AppSettingsState {
    isLoading: boolean;
    appSettings: AppSettings;
}

export interface AppSettings {
    SiteTitle: string;
    PrimaryBackgroundColor: string;
    PrimaryFontColor: string;
    FooterLinkColor: string;
} 

// ACTIONS - descriptions of state transitions
interface RequestAppSettingsAction {
    type: 'REQUEST_APP_SETTINGS';
}

interface ReceiveAppSettingsAction {
    type: 'RECEIVE_APP_SETTINGS';
    appSettings: AppSettings;
}

/* Declare a 'discriminated union' type. */
type KnownAction = RequestAppSettingsAction | ReceiveAppSettingsAction;

// ACTION CREATORS - These are functions exposed to UI components that will trigger a state transition.
// They don't directly mutate state, but they can have external side-effects (such as loading data).

export const actionCreators = {
    requestAppSettings: (): AppThunkAction<KnownAction> => (dispatch, getState) => {
        /* Only load data if it's something we don't already have (and are not already loading) */
        console.log("AppSettings fired");
        const appState = getState();
        if (appState && appState.appSettings) {
            fetch(`appsettings`)
                .then(response => response.json() as Promise<AppSettings>)
                .then(data => {
                    dispatch({ type: 'RECEIVE_APP_SETTINGS', appSettings: data });
                });

            dispatch({ type: 'REQUEST_APP_SETTINGS'});
        }
    }
};

/*
REDUCER - For a given state and action, returns the new state.
To support time travel, this must not mutate the old state.
*/

const unloadedState: AppSettingsState = {
    isLoading: false,
    appSettings: {
        SiteTitle: "",
        PrimaryBackgroundColor: "",
        PrimaryFontColor: "",
        FooterLinkColor: ""
    }
};

export const reducer: Reducer<AppSettingsState> = (state: AppSettingsState | undefined, incomingAction: Action): AppSettingsState => {
    if (state === undefined) {
        return unloadedState;
    }

    const action = incomingAction as KnownAction;
    switch (action.type) {
        case 'REQUEST_APP_SETTINGS':
            return {
                appSettings: state.appSettings,
                isLoading: true
            };
        case 'RECEIVE_APP_SETTINGS':
            return {
                appSettings: action.appSettings,
                isLoading: false
            };
            break;
    }

    return state;
};

User.ts

import { Action, Reducer } from 'redux';
import { isNullOrUndefined } from 'util';
import { AppThunkAction } from './';
import * as Cookies from '../Utilities/cookies';

export interface UserState {
    isLoading: boolean;
    loginError: string;
    loggedIn: boolean;
    user: User;
}

export interface User {
    username: string;
    email: string;
    firstName: string;
    lastName: string;
    token: string
} 

interface RequestUserAction {
    type: 'REQUEST_USER';
}

interface ReceiveUserAction {
    type: 'RECEIVE_USER';
    loginError: string;
    user: User;
}

type KnownAction = RequestUserAction | ReceiveUserAction;

export const actionCreators = {
    requestUser: (event?: React.FormEvent<HTMLFormElement>) : AppThunkAction<KnownAction> => (dispatch, getState) => {

        const appState = getState();
        var url = "";
        var skip = false;
        var params = {};

        /* Two ways to get user info - they submitted the login form
         * or they are already logged in and have a user object cookie */

        if (event != undefined) {

            event.preventDefault();
            const target = event.target as typeof event.target & {
                username: { value: string };
                password: { value: string };
            };

            const username = target.username.value;
            const password = target.password.value;

            url = "user/login";
            params = {
                method: "POST",
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ username: username, password: password })
            }

        } else {

            var cUser: string | null = Cookies.getCookie("user");
            if (cUser) {
                var user: User = JSON.parse(cUser);
                url = "user";
                params = {
                    method: "GET",
                    headers: {
                        'Content-Type': 'application/json',
                        'Authorization': 'Bearer ' + user.token
                    }
                }

            } else {
                skip = true; // home page and never has or tried to login
            }
        }

        if (appState && appState.user && !skip) {
            fetch(url, params)
                .then(response => {
                    if (!response.ok) {
                        throw new Error(response.status.toString());
                    } else {
                        return response.json() as Promise<User>;
                    }
                })
                .then(data => {
                    dispatch({ type: 'RECEIVE_USER', user: data, loginError: "" });
                })
                .catch(error => {
                    dispatch({ type: 'RECEIVE_USER', user: {} as User, loginError: error.message });
                });

            dispatch({ type: 'REQUEST_USER' });
        }
    }
};

const unloadedState: UserState = {
    isLoading: false,
    loginError: "",
    loggedIn: false,
    user: {
        username: "",
        email: "",
        firstName: "",
        lastName: "",
        token: ""
    }
};

export const reducer: Reducer<UserState> = (state: UserState | undefined, incomingAction: Action): UserState => {
    if (state === undefined) {
        return unloadedState;
    }

    const action = incomingAction as KnownAction;
    switch (action.type) {
        case 'REQUEST_USER':
            return {
                user: state.user,
                loginError: "",
                loggedIn: false,
                isLoading: true,
            };
        case 'RECEIVE_USER':

            var loggedIn = false;

            if (!action.loginError) {
                loggedIn = true;
                Cookies.setCookie("user", JSON.stringify(action.user), 7);
            } else {
                loggedIn = false;
                Cookies.eraseCookie("user");
            }

            return {
                user: action.user,
                loginError: action.loginError,
                loggedIn: loggedIn,
                isLoading: false
            };
            break;
    }

    return state;
};

App.tsx

import * as React from 'react';
import { Route } from 'react-router';
import Layout from './components/Layout';
import Home from './components/Home';
import Counter from './components/Counter';
import FetchData from './components/FetchData';
import CapacityGrid from './components/CapacityGrid';
import { connect } from 'react-redux';
import { Container } from 'reactstrap';
import { ApplicationState } from './store';
import * as AppSettingsStore from './store/AppSettings';
import * as UserStore from './store/User';
import './css/custom.css'
import { userInfo } from 'os';

type AppSettingsProps =
    AppSettingsStore.AppSettingsState
    & typeof AppSettingsStore.actionCreators
 
type UserProps =
    UserStore.UserState
    & typeof UserStore.actionCreators

class App extends React.PureComponent<UserProps & AppSettingsProps & { children?: React.ReactNode }> {

    public componentDidMount() {
        this.fetchAppSettings();
        this.fetchUser();
    }

    private fetchAppSettings() {
        this.props.requestAppSettings();
    }

    private fetchUser() {
        this.props.requestUser();
    }

    public render() {
        return (
            <React.Fragment>
                {!this.props.isLoading &&
                    <Layout>
                        <Route path='/capacity-grid' component={CapacityGrid} />
                        <Route exact path='/' component={Home} />
                        <Route path='/counter' component={Counter} />
                        <Route path='/fetch-data/:startDateIndex?' component={FetchData} />
                    </Layout>
                }
            </React.Fragment>
        )
    }

}

/* works 
export default connect(
    (state: ApplicationState) => state.user,
    UserStore.actionCreators
)(App as any); */

/*const mapDispatchToProps = (dispatch: any) => ({
    userActions: () => dispatch(UserStore.actionCreators.requestUser),
    appActions: () => dispatch(AppSettingsStore.actionCreators.requestAppSettings),
});*/

const mapStateToProps = (state: ApplicationState) => ({
    user: state.user,
    appSettings: state.appSettings
});

const mapDispatchToProps = (
    UserStore.actionCreators,
    AppSettingsStore.actionCreators
);

export default connect(mapStateToProps, mapDispatchToProps)(App as any);

【问题讨论】:

    标签: javascript reactjs typescript redux


    【解决方案1】:

    这行代码

    const mapDispatchToProps = (
       UserStore.actionCreators,
       AppSettingsStore.actionCreators
    );
    

    等价于

    const mapDispatchToProps = AppSettingsStore.actionCreators
    

    意味着用户的操作创建者没有被传递给 App.ts。你应该也可以在 React devtools 中验证这一点。

    这是一个小的codesandbox,它指出了问题所在。

    要解决此问题,您需要定义 mapDispatchToProps,因为您已注释掉。

    const mapDispatchToProps = (dispatch: any) => ({
      userActions: () => dispatch(UserStore.actionCreators.requestUser),
      appActions: () => 
        dispatch(AppSettingsStore.actionCreators.requestAppSettings),
    });
    

    【讨论】:

      【解决方案2】:

      我能够弄清楚。我需要使用扩展运算符在我的 ma​​tchDispatchToProps

      中获取两个 actionCreators

      我改变了这个

      const mapStateToProps = (state: ApplicationState) => ({
          user: state.user,
          appSettings: state.appSettings
      });
      
      const mapDispatchToProps = (
          UserStore.actionCreators,
          AppSettingsStore.actionCreators
      );
      

      到这里

      const mapStateToProps = (state: ApplicationState) => ({
          ...state.user,
          ...state.appSettings
      });
      
      const mapDispatchToProps = ({
          ...UserStore.actionCreators,
          ...AppSettingsStore.actionCreators
      });
      

      【讨论】:

        猜你喜欢
        • 2018-07-17
        • 2018-02-24
        • 2019-10-23
        • 2019-06-14
        • 2017-08-23
        • 2017-08-15
        • 2021-12-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多