Redux-Observable和Rxjs没有捕获第一个事件但是捕获了第二个事件

时间:2018-02-12 15:21:48

标签: reactjs typescript redux rxjs observable

我一直致力于使用Typescript编写的聊天机器人应用程序,并使用带有rxjs的Redux-Observable。我试图从Shell.tsx组件发送一个click事件到我的redux商店,它首先被rxjs Epic截获,然后被发送到redux商店。但是我的redux商店的返回状态不会影响应该对点击事件做出的更改,但是它确实会在第二次点击时返回预期的结果。

这是我的Shell.tsx的一部分,它包含将click事件作为对商店的操作触发的相关组件方法:

    import * as React from 'react';
    import { ChatState, FormatState } from './Store';
    import { User } from 'botframework-directlinejs';
    import { classList } from './Chat';
    import { Dispatch, connect } from 'react-redux';
    import { Strings } from './Strings';
    import { createStore, ChatActions, sendMessage } from './Store';

    import { Subscription } from 'rxjs/Subscription';
    import { Observable } from 'rxjs/Observable';
    import 'rxjs/add/observable/fromEvent';
    import 'rxjs/add/observable/merge';

interface Props {
    inputText: string,
    strings: Strings,
    isActive: boolean,

    onChangeText: (inputText: string) => void,

    sendMessage: (inputText: string) => void
    checkActive: (isChatActive: boolean) => void
}

private handleChatClick(isChatActive) {
        this.store.dispatch({type: 'Chat_Activate', isChatActive: true})
        setTimeout(() => {
            this.store.subscribe(() => {
                this.isActive = this.store.getState().shell.isChatActive
            })
            console.log(this.isActive)
        }, 3000)
        if (this.isActive) {
            this.forceUpdate()
        }
        // this.props.checkActive(true)
    }

render() {

        //other code

        return (
            <div className={ className }>
                <div className="wc-textbox">
                {
                    console.log('chat rendered')
                }
                    <input
                        type="text"
                        className="wc-shellinput"
                        ref={ input => this.textInput = input }
                        value={ this.props.inputText }
                        onChange={ _ => this.props.onChangeText(this.textInput.value) }
                        onKeyPress={ e => this.onKeyPress(e) }
                        placeholder={ placeholder }
                        aria-label={ this.props.inputText ? null : placeholder }
                        aria-live="polite"
                        // onFocus={ this.props.handleChatClick}
                        onClick={() => {
                            this.handleChatClick(true)
                        }}
                    />
                </div>
            </div>
        );
    }


export const Shell = connect(
    (state, ownProps) => {
        return {
            inputText: state.shell.input,
            strings: state.format.strings,
            isActive: state.shell.isChatActive,

            // only used to create helper functions below
            locale: state.format.locale,
            user: state.connection.user
        }
    }
    , {
        // passed down to ShellContainer
        onChangeText: (input: string) => ({ type: 'Update_Input', input, source: "text" } as ChatActions),
        // only used to create helper functions below
        sendMessage
    }, (stateProps: any, dispatchProps: any, ownProps: any): Props => ({
        // from stateProps
        inputText: stateProps.inputText,
        strings: stateProps.strings,
        isActive: stateProps.isActive,
        // from dispatchProps
        onChangeText: dispatchProps.onChangeText,
        checkActive: dispatchProps.checkActive,
        // helper functions
        sendMessage: (text: string) => dispatchProps.sendMessage(text, stateProps.user, stateProps.locale),
    }), {
        withRef: true
    }
)(ShellContainer);

这是我的Store.ts代码的一部分:

export interface ShellState {
    sendTyping: boolean
    input: string,
    isChatActive: boolean,
    isPinging: boolean
}

export const setChatToActive = (isChatActive: boolean) => ({
    type: 'Chat_Activate',
    isChatActive: isChatActive,
    } as ChatActions);

export const ping = (isPinging: boolean) => ({
    type: 'Is_Pinging',
    isPinging: isPinging
} as ChatActions)

export type ShellAction = {
    type: 'Update_Input',
    input: string
    source: "text"
} |  {
    type: 'Card_Action_Clicked'
} | {
    type: 'Set_Send_Typing',
    sendTyping: boolean
} | {
    type: 'Send_Message', 
    activity: Activity
} | {
    type: 'Chat_Activate',
    isChatActive: boolean
} | {
    type: 'Is_Pinging',
    isPinging: boolean
}


export const shell: Reducer<ShellState> = (
    state: ShellState = {
        input: '',
        sendTyping: false,
        isChatActive: false,
        isPinging: false
    },
    action: ShellAction
) => {
    console.log(state)
    switch (action.type) {
        case 'Update_Input':
            return {
                ... state,
                input: action.input
            };
        case 'Send_Message':
            return {
                ... state,
                input: ''
            };
        case 'Chat_Activate':
            const newState = {
                ...state,
                isChatActive: action.isChatActive
            }
            return newState
        case 'Set_Send_Typing':
            return {
                ... state,
                sendTyping: action.sendTyping
            };

        case 'Card_Action_Clicked':
           return {
               ... state
           };
        case 'Is_Pinging':
            const newPing = {
                ... state,
                isPinging: action.isPinging
            }
            return newPing;

        default:
        return state;
    }
}




// 2. Epics 

//************************************************************************
//Import modules
import { applyMiddleware } from 'redux';
import { Epic } from 'redux-observable';
import { Observable } from 'rxjs/Observable';

import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/delay';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/merge';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/mapTo';

import 'rxjs/add/operator/throttleTime';
import 'rxjs/add/operator/takeUntil';

import 'rxjs/add/observable/bindCallback';
import 'rxjs/add/observable/empty';
import 'rxjs/add/observable/of';

//************************************************************************
//Asynchronously send messages
const sendMessageEpic: Epic<ChatActions, ChatState> = (action$, store) =>
    action$.ofType('Send_Message')
    .map(action => {
        const state = store.getState();
        const clientActivityId = state.history.clientActivityBase + (state.history.clientActivityCounter - 1);
        return ({ type: 'Send_Message_Try', clientActivityId } as HistoryAction);
    });

const setChatToActiveEpic: Epic<ChatActions, ChatState> = (action$, store) =>
    action$.ofType('Chat_Activate')
    .mapTo({type: 'Chat_Activate', isChatActive: true} as ChatActions)
    .takeUntil(
        action$.ofType('Chat_Activate')
    )

const pingEpic: Epic<ChatActions, ChatState> = (action$, store) => 
    action$.ofType('Is_Pinging')
    .mapTo({type: 'Is_Pinging', isPinging: true} as ChatActions)
    .takeUntil(
        action$.ofType('Is_Pinging')
    )


// 3. Now we put it all together into a store with middleware

import { Store, createStore as reduxCreateStore, combineReducers } from 'redux';
import { combineEpics, createEpicMiddleware } from 'redux-observable';

export const createStore = () =>
    reduxCreateStore(
        combineReducers<ChatState>({
            shell,
            format,
            size,
            connection,
            history      
        }),
        applyMiddleware(createEpicMiddleware(combineEpics(
            updateSelectedActivityEpic,
            sendMessageEpic,
            trySendMessageEpic,
            retrySendMessageEpic,
            showTypingEpic,
            sendTypingEpic,
            setChatToActiveEpic,
            pingEpic
        )))
    );

export type ChatStore = Store<ChatState>;

简而言之,当我单击Shell.tsx中的input元素时,我想生成一个true的控制台日志。但是当我第一次点击输入时输出总是假的,当我再次点击它时就会起作用。

console log results

1 个答案:

答案 0 :(得分:1)

我的代码中没有看到任何错误,导致在第一次调度操作时不会更改状态。日志记录有点令人困惑,所以它可能会让你认为代码的行为与它不同?

从你的截图中,我可以看到第一个console.log语句来自store.ts第84行(如果你一直向右看,你可以看到),第二个console.log来自组件。

enter image description here

在store.ts文件中,您在reducer顶部有一个console.log语句。由于此日志记录位于reducer的顶部,因此它将始终显示先前的状态,而不是更新的状态。

export const shell: Reducer<ShellState> = (
    state: ShellState = {
        input: '',
        sendTyping: false,
        isChatActive: false,
        isPinging: false
    },
    action: ShellAction
) => {
    console.log(state)

另一件令您感到困惑的事情是,您在更改商店后正在收听商店更改。

// this updates the store
this.store.dispatch({type: 'Chat_Activate', isChatActive: true})
// 3 seconds later, you're listening for store changes, but it's already changed
setTimeout(() => {
    this.store.subscribe(() => {
        this.isActive = this.store.getState().shell.isChatActive
    })
    // then you console.log this.isActive which might be false because that's the initial state in the reducer
    console.log(this.isActive)
}, 3000)

如果您希望更改操作,则应在订阅操作之前订阅商店。

this.store.subscribe(() => {
    this.isActive = this.store.getState().shell.isChatActive;
});
this.store.dispatch({type: 'Chat_Activate', isChatActive: true});

或者,您可以使用React-Redux中的Connected组件。他们会自动监听商店更改并自动更新组件,这样您就不必自己订阅商店进行更改。

如果您希望立即使用console.log查看商店值更新,您可以执行类似

的操作
private handleChatClick(isChatActive) {
    console.log(`before click/chat active event: ${this.store.getState().shell.isChatActive}`);
    this.store.dispatch({type: 'Chat_Activate', isChatActive: true})
    console.log(`after click/chat active event: ${this.store.getState().shell.isChatActive}`);
}