为什么我不能从我的事件处理程序函数调用我的 Redux 操作创建者,但我可以从 componentWillMount() 调用?

Why can't I call my Redux action creators from my event handler function but I can from componentWillMount()?

我正在使用 React、Redux 和 TypeScript 开发应用程序。

我有一个 Redux 存储设置,它设置初始状态并成功填充我的组件。我现在正在尝试连接一个表单,该表单将调用我的操作创建者,该创建者从具有过滤值的控制器中检索数据。

我正在使用连接通过道具访问动作创建者。

我可以从 componentWillMount() 调用我的动作创建者,但如果我在我的表单提交处理程序函数中进行相同的调用,我会收到此错误:

Uncaught TypeError: Cannot read property 'requestVersions' of undefined

这是我启用了 Redux 的组件:

import * as React from 'react';
import { Link, NavLink, RouteComponentProps } from 'react-router-dom';
import { connect } from 'react-redux';
import { ApplicationState } from '../../store';
import * as ASVersionQueueState from '../../store/ASVersionQueue';
import { AgGridReact } from 'ag-grid-react';
import SimpleGridExample from "./SimpleGridExample";
import ASVersionQueueBulkActions from "./ASVersionQueueBulkActions";
import ASVersionQueueFilters from "./ASVersionQueueFilters";
import DataGridStats from "../common/DataGridStats";
import PageTitleHeader from "../common/PageTitleHeader";

type ASVersionQueueProps =
    ASVersionQueueState.ASVersionQueueState             // ... state we've requested from the Redux store
    & typeof ASVersionQueueState.actionCreators       // ... plus action creators we've requested
    & RouteComponentProps<{}>;                      // ... plus incoming routing parameters

class ASVersionQueue extends React.Component<ASVersionQueueProps, {}> {

    handleChange(event: any) {
        console.log('Bulk Action: ' + event.target.value);
        console.log()
    }

    componentWillMount() {
        // This method runs when the component is first added to the page
        // This function call works
        this.props.requestVersions(this.props.versionQueueFilter);
        this.props.requestEmployeesList();
        this.props.requestEventsList();
    }

    handleEventsFilterChange(event: any) {
        console.log('Event Filter: ' + event.target.value);
    }

    handleFilterSubmission() { 
        // I removed my incoming parameters and am simply passing back the current state values to understand the source of my error
        //This function call is throwing the error
        this.props.requestVersions(this.props.versionQueueFilter);
    }

    public render() {
        return <div className="container-fluid">
            <PageTitleHeader
                title="Account Services"
                className="page-title"
            />
            <div className="as-grid grid-controls-container row">
                <div className="col-lg-2 bulk-actions-container">
                    <ASVersionQueueBulkActions
                        outerClassName=""
                        controlClassName=""
                        htmlId="ver-grid-bulk-action"
                        htmlName="ver-grid-bulk-action"
                        onChangeFunction={() => (this.handleChange)}

                    // TO DO: pass down menu options to trigger Redux actions

                    />
                </div>
                <div className="col-lg-8 grid-filter-container">
                    <ASVersionQueueFilters 
                        outerClassName=""
                        htmlId=""
                        filterVals={this.props.versionQueueFilter}
                        employeesFilterList={this.props.employeesFilterList}
                        eventsFilterList={this.props.eventsFilterList}
                        onSubmitFunction={this.handleFilterSubmission}
                    />
                </div>
                <div className="col-lg-2 grid-stats-container">
                    <DataGridStats />
                </div>
            </div>
            <div className="ag-grid full row ag-theme-blue">
                <SimpleGridExample />
            </div>
        </div>;
    }
}

// Wire up the React component to the Redux store
export default connect(
    (state: ApplicationState) => state.asVersionQueue,     // Selects which state properties are merged into the component's props
    ASVersionQueueState.actionCreators                 // Selects which action creators are merged into the component's props
)(ASVersionQueue) as typeof ASVersionQueue;

这是我的 Redux action creators 和 reducers:

import { fetch, addTask } from 'domain-task';
import { Action, Reducer, ActionCreator } from 'redux';
import { AppThunkAction } from './';
import * as moment from 'moment';

// -----------------
// STATE - This defines the type of data maintained in the Redux store.

export interface ASVersionQueueState {
    queuedVersions: QueuedVersion[];
    versionQueueFilter: VersionQueueFilter;
    eventsFilterList: SelectListItem[];
    employeesFilterList: SelectListItem[];
}

export interface QueuedVersion {
    VersionCode: string;
    VersionQualifier: string;
    VersionID: string;
    ProductID: string;
    PieceName: string;
    PrintClass: string;
    FirstInhomeDate: string;
    AccountID: string;
    AccountExecutive: string;
    AccountManager: string;
    ArtManager: string;
    AdUID: string;
    Status: string;
    Queue: string;
    DueDateOverride: string;
    IsLocked: string;
}

export interface VersionQueueFilter {
    StartDate: string;
    EndDate: string;
    PieceType: Array<string>;
    EventType: Array<string>;
    EventID: string;
    Employee: string;
}

export interface SelectListItem {
    OptionName: string;
    OptionVal: string;
}

let DefaultVersionQueueFilter = {
    StartDate: moment().subtract(30, 'days').format('YYYY-MM-DD'),
    EndDate: moment().format('YYYY-MM-DD'),
    PieceType: ['pt-impactpc'],
    EventType: ['et-special'],
    EventID: '',
    Employee: '12345'
}


// -----------------
// ACTIONS - These are serializable (hence replayable) descriptions of state transitions.
// They do not themselves have any side-effects; they just describe something that is going to happen.
// Use @typeName and isActionType for type detection that works even after serialization/deserialization.

interface RequestVersionsAction {
    type: 'REQUEST_VERSIONS';
    versionQueueFilter: VersionQueueFilter;
}

interface ReceiveVersionsAction {
    type: 'RECEIVE_VERSIONS';
    versionQueueFilter: VersionQueueFilter;
    receivedVersions: QueuedVersion[];
}

interface RequestEmployeesListAction {
    type: 'REQUEST_EMPLOYEES_LIST';
}

interface ReceiveEmployeesListAction {
    type: 'RECEIVE_EMPLOYEES_LIST';
    receivedEmployeesList: SelectListItem[];
}

interface RequestEventsListAction {
    type: 'REQUEST_EVENTS_LIST';
}

interface ReceiveEventsListAction {
    type: 'RECEIVE_EVENTS_LIST';
    receivedEventsList: SelectListItem[];
}


// Declare a 'discriminated union' type. This guarantees that all references to 'type' properties contain one of the
// declared type strings (and not any other arbitrary string).
type KnownAction = RequestVersionsAction | ReceiveVersionsAction | RequestEmployeesListAction | ReceiveEmployeesListAction | RequestEventsListAction | ReceiveEventsListAction;

// ----------------
// 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 = {
    requestVersions: (versionQueueFilter: VersionQueueFilter): AppThunkAction<KnownAction> => (dispatch, getState) => {
        console.log('actionCreator requestVersions called...');
        if (versionQueueFilter !== getState().asVersionQueue.versionQueueFilter) {
            let fetchTask = fetch(`api/Versions`)
                .then(response => response.json() as Promise<QueuedVersion[]>)
                .then(data => {
                    dispatch({ type: 'RECEIVE_VERSIONS', versionQueueFilter: versionQueueFilter, receivedVersions: data });
                    //dispatch({ type: 'RECEIVE_VERSIONS', receivedVersions: data });
                });

            addTask(fetchTask); // Ensure server-side prerendering waits for this to complete
            dispatch({ type: 'REQUEST_VERSIONS', versionQueueFilter: versionQueueFilter });
            //dispatch({ type: 'REQUEST_VERSIONS' });
        }
    },
    requestEmployeesList: (): AppThunkAction<KnownAction> => (dispatch, getState) => {
        let fetchTask = fetch(`api/EmployeesList`)
            .then(response => response.json() as Promise<SelectListItem[]>)
            .then(data => {
                dispatch({ type: 'RECEIVE_EMPLOYEES_LIST', receivedEmployeesList: data });
            });

        addTask(fetchTask); // Ensure server-side prerendering waits for this to complete
        dispatch({ type: 'REQUEST_EMPLOYEES_LIST' });
    },
    requestEventsList: (): AppThunkAction<KnownAction> => (dispatch, getState) => {
        let fetchTask = fetch(`api/EventsLIst`)
            .then(response => response.json() as Promise<SelectListItem[]>)
            .then(data => {
                dispatch({ type: 'RECEIVE_EVENTS_LIST', receivedEventsList: data });
            });

        addTask(fetchTask); // Ensure server-side prerendering waits for this to complete
        dispatch({ type: 'REQUEST_EVENTS_LIST' });
    }
};

// ----------------
// REDUCER - For a given state and action, returns the new state. To support time travel, this must not mutate the old state.
const unloadedState: ASVersionQueueState = { queuedVersions: [], versionQueueFilter: DefaultVersionQueueFilter, eventsFilterList: [], employeesFilterList: [] };

export const reducer: Reducer<ASVersionQueueState> = (state: ASVersionQueueState, incomingAction: Action) => {
    const action = incomingAction as KnownAction;
    switch (action.type) {
        case 'REQUEST_EMPLOYEES_LIST':
            return {
                queuedVersions: state.queuedVersions,
                versionQueueFilter: state.versionQueueFilter,
                eventsFilterList: state.eventsFilterList,
                employeesFilterList: state.employeesFilterList
            }
        case 'REQUEST_EVENTS_LIST':
            return {
                queuedVersions: state.queuedVersions,
                versionQueueFilter: state.versionQueueFilter,
                eventsFilterList: state.eventsFilterList,
                employeesFilterList: state.employeesFilterList
            }
        case 'REQUEST_VERSIONS':
            return {
                queuedVersions: state.queuedVersions,
                versionQueueFilter: state.versionQueueFilter,
                eventsFilterList: state.eventsFilterList,
                employeesFilterList: state.employeesFilterList
            }
        case 'RECEIVE_EMPLOYEES_LIST':
            return {
                queuedVersions: state.queuedVersions,
                versionQueueFilter: state.versionQueueFilter,
                eventsFilterList: state.eventsFilterList,
                employeesFilterList: action.receivedEmployeesList
            }
        case 'RECEIVE_EVENTS_LIST':
            return {
                queuedVersions: state.queuedVersions,
                versionQueueFilter: state.versionQueueFilter,
                eventsFilterList: action.receivedEventsList,
                employeesFilterList: state.employeesFilterList
            }
        case 'RECEIVE_VERSIONS':
            // Only accept the incoming data if it matches the most recent request. This ensures we correctly
            // handle out-of-order responses.
            if (action.versionQueueFilter === state.versionQueueFilter) {
                return {
                    queuedVersions: action.receivedVersions,
                    versionQueueFilter: action.versionQueueFilter,
                    eventsFilterList: state.eventsFilterList,
                    employeesFilterList: state.employeesFilterList
                };
            }
            break;
        default:
            // The following line guarantees that every action in the KnownAction union has been covered by a case above
            const exhaustiveCheck: never = action;
    }

    return state || unloadedState;
};

在这里调用我的动作创建器有效:

componentWillMount() {
        this.props.requestVersions(this.props.versionQueueFilter);
    }

在这里调用我的动作创建器会导致错误,我不明白为什么:

handleFilterSubmission() { 
        this.props.requestVersions(this.props.versionQueueFilter);
    }

注意:我删除了提交处理程序上的函数参数,只是为了确认我对传递的数据类型没有问题。

我正在从子组件调用该函数,我正在将引用传递给该函数。

这是父组件中的子组件引用:

<ASVersionQueueFilters 
    outerClassName=""
    htmlId=""
    filterVals={this.props.versionQueueFilter}
    employeesFilterList={this.props.employeesFilterList}
    eventsFilterList={this.props.eventsFilterList}
    onSubmitFunction={this.handleFilterSubmission}
/>

在子组件中,我已将传递的函数 "onSubmitFunction" 绑定到最终调用 onSubmitFunction 的事件处理函数:

handleFormSubmit(event: React.FormEvent<EventTarget>) {
    event.preventDefault;
    let filterVals = {
        StartDate: moment(this.state.startDate).format('YYYY-MM-DD'),
        EndDate: moment(this.state.endDate).format('YYYY-MM-DD'),
        PieceType: [],
        EventType: [],
        EventID: this.state.selectedEventID,
        Employee: this.state.selectedEmployee
    }
    this.props.onSubmitFunction(filterVals);
}

按钮触发函数,我可以在控制台中看到过滤器值已成功传递回父函数,但以这种方式调用我的动作创建者会不断抛出错误。

知道我做错了什么吗?

您需要将 handleFilterSubmissionthis 绑定。为此,您可以创建这样的构造函数:

constructor(props) {
    super(props);

    this.handleFilterSubmission = this.handleFilterSubmission.bind(this);
}