AspNet Core SignalR和Redux配置-发送和接收数据

时间:2020-06-03 05:16:36

标签: javascript redux react-redux asp.net-core-signalr ecmascript-next

我是使用AspNet Core SiginalR和React.js + Redux的聊天功能。

解决问题的步骤
我可以通过signalR发送消息到后端服务器
我可以在中间件上收到消息
但是我不愿意调度以更新存储和更新视图中的状态。

问题
我做错了什么?

也许我无法从回调 connection.on(“ ReceiveMessage”,data => ...

中访问调度功能

是吗?如何解决?

应用



    import '@fake-db'
    import React, {Suspense} from 'react';
    import {FuseAuthorization, FuseLayout, FuseTheme} from '@fuse';
    import Provider from 'react-redux/es/components/Provider';
    import {Router} from 'react-router-dom';
    import jssExtend from 'jss-extend';
    import history from '@history';
    import {Auth} from './auth';
    import store from './store';
    import AppContext from './AppContext';
    import routes from './fuse-configs/routesConfig';
    import {create} from 'jss';
    import {StylesProvider, jssPreset, createGenerateClassName} from '@material-ui/styles';
    import axios from 'axios';

    const jss = create({
        ...jssPreset(),
        plugins       : [...jssPreset().plugins, jssExtend()],
        insertionPoint: document.getElementById('jss-insertion-point'),
    });

    axios.defaults.baseURL = 'https://localhost:5001/api/v1/';

    const generateClassName = createGenerateClassName();

    const App = () => {
        return (
            <Suspense fallback="loading">
                <AppContext.Provider value={{routes}}>
                    <StylesProvider jss={jss} generateClassName={generateClassName}>
                        <Provider store={store}>
                            <Auth>
                                <Router history={history}>
                                    <FuseAuthorization>
                                        <FuseTheme>
                                            <FuseLayout/>
                                        </FuseTheme>
                                    </FuseAuthorization>
                                </Router>
                            </Auth>
                        </Provider>
                    </StylesProvider>
                </AppContext.Provider>
            </Suspense>
        );
    };

    export default App;

创建商店


    import * as reduxModule from 'redux';
    import {applyMiddleware, compose, createStore} from 'redux';
    import createReducer from './reducers';
    import signalRMiddleware from './middlewares/signalRMiddleware';
    import thunk from 'redux-thunk';

    /*
    Fix for Firefox redux dev tools extension
    https://github.com/zalmoxisus/redux-devtools-instrument/pull/19#issuecomment-400637274
     */
    reduxModule.__DO_NOT_USE__ActionTypes.REPLACE = '@@redux/INIT';

    const composeEnhancers =
        process.env.NODE_ENV !== 'production' &&
        typeof window === 'object' &&
        window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
            window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
                // Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
            }) : compose;

    const enhancer = composeEnhancers(
        applyMiddleware(
            thunk,
            signalRMiddleware
            )
        // other store enhancers if any
    );

    const store = createStore(createReducer(), enhancer);

    store.asyncReducers = {};

    export const injectReducer = (key, reducer) => {
        if ( store.asyncReducers[key] )
        {
            return;
        }
        store.asyncReducers[key] = reducer;
        store.replaceReducer(createReducer(store.asyncReducers));
        return store;
    };

    export default store;

中间件文件


    import { HubConnectionBuilder, LogLevel } from '@microsoft/signalr';
    import * as Actions from "../../main/apps/chat/store/actions";

    const connection = new HubConnectionBuilder().withUrl("https://localhost:5001/chatHub")
                                            .configureLogging(LogLevel.Information)
                                            .withAutomaticReconnect()
                                            .build();

    connection.start();

    export default function signalRMiddleware() { 

            connection.on('ReceiveMessage', data => {
                Actions.receiveSocketMessage(data)
            })

            return  next => action => {

            switch (action.type) {

                case Actions.DIRECT_MESSAGE: 
                    connection.invoke('DirectMessage', action.payload);
            }

            return next(action);
        }
    }

聊天操作


    import axios from 'axios';
    import {setselectedContactId} from './contacts.actions';
    import {closeMobileChatsSidebar} from './sidebars.actions';

    export const GET_CHAT = '[CHAT APP] GET CHAT';
    export const REMOVE_CHAT = '[CHAT APP] REMOVE CHAT';
    export const SEND_MESSAGE = '[CHAT APP] SEND MESSAGE';
    export const DIRECT_MESSAGE = '[CHAT APP] DIRECT MESSAGE';
    export const RECEIVE_MESSAGE = '[CHAT APP] RECEIVE MESSAGE';

    export function receiveSocketMessage(data)
    {
        return (dispatch) => {
            dispatch({type: RECEIVE_MESSAGE, payload: `test`})
        }
    }

    export function directMessage(data)
    {
        return {
            type : DIRECT_MESSAGE,
            payload : data
        }
    }

聊天记录减少器


    import * as Actions from '../actions';

    const initialState = null;

    const chat = function (state = initialState, action) {
        switch ( action.type )
        {
            case Actions.RECEIVE_MESSAGE:
            {
                return {
                    ...state,
                    directMessage: action.payload
                }
            }
            default:
            {
                return state;
            }
        }
    };

    export default chat;

聊天组件


    import React, {useEffect} from 'react';
    import {useTranslation} from 'react-i18next';
    import {Drawer, AppBar, Toolbar, Typography, IconButton, Hidden, Avatar, Icon, Paper, Button} from '@material-ui/core';
    import {fade} from '@material-ui/core/styles/colorManipulator';
    import {useDispatch, useSelector} from 'react-redux';
    import clsx from 'clsx';
    import withReducer from 'app/store/withReducer';
    import * as Actions from "./store/actions";
    import Chat from "./Chat";
    import ChatsSidebar from "./ChatsSidebar";
    import StatusIcon from "./StatusIcon";
    import ContactSidebar from './ContactSidebar';
    import UserSidebar from './UserSidebar';
    import reducer from './store/reducers';
    import {makeStyles} from '@material-ui/styles';

    const drawerWidth = 400;
    const headerHeight = 200;

    const useStyles = makeStyles(theme => ({
        root              : {
            display        : 'flex',
            flexDirection  : 'row',
            minHeight      : '100%',
            position       : 'relative',
            flex           : '1 1 auto',
            height         : 'auto',
            backgroundColor: theme.palette.background.default
        },
        topBg             : {
            position       : 'absolute',
            left           : 0,
            right          : 0,
            top            : 0,
            height         : headerHeight,
            backgroundImage: 'url("../../assets/images/backgrounds/header-bg.png")',
            backgroundColor: theme.palette.primary.dark,
            backgroundSize : 'cover',
            pointerEvents  : 'none'
        },
        contentCardWrapper: {
            position                      : 'relative',
            padding                       : 24,
            maxWidth                      : 1400,
            display                       : 'flex',
            flexDirection                 : 'column',
            flex                          : '1 0 auto',
            width                         : '100%',
            minWidth                      : '0',
            maxHeight                     : '95%',
            margin                        : '0 auto',
            [theme.breakpoints.down('sm')]: {
                padding: 16
            },
            [theme.breakpoints.down('xs')]: {
                padding: 12
            }
        },
        contentCard       : {
            display        : 'flex',
            position       : 'relative',
            flex           : '1 1 100%',
            flexDirection  : 'row',
            backgroundColor: "f7f7f7",
            boxShadow      : theme.shadows[1],
            borderRadius   : 8,
            minHeight      : 0,
            overflow       : 'hidden'
        },
        drawerPaper       : {
            width                       : drawerWidth,
            maxWidth                    : '100%',
            overflow                    : 'hidden',
            height                      : '100%',
            [theme.breakpoints.up('md')]: {
                position: 'relative'
            }
        },
        contentWrapper    : {
            display      : 'flex',
            flexDirection: 'column',
            flex         : '1 1 100%',
            zIndex       : 10,
            background   : `linear-gradient(to bottom, ${fade(theme.palette.background.paper, 0.8)} 0,${fade(theme.palette.background.paper, 0.6)} 20%,${fade(theme.palette.background.paper, 0.8)})`
        },
        content           : {
            display  : 'flex',
            flex     : '1 1 100%',
            minHeight: 0
        }
    }));

    function ChatApp(props)
    {

        const { t } = useTranslation();
        const dispatch = useDispatch();
        const chat = useSelector(({chatApp}) => chatApp.chat);
        const contacts = useSelector(({chatApp}) => chatApp.contacts.entities);
        const selectedContactId = useSelector(({chatApp}) => chatApp.contacts.selectedContactId);
        const mobileChatsSidebarOpen = useSelector(({chatApp}) => chatApp.sidebars.mobileChatsSidebarOpen);
        const userSidebarOpen = useSelector(({chatApp}) => chatApp.sidebars.userSidebarOpen);
        const contactSidebarOpen = useSelector(({chatApp}) => chatApp.sidebars.contactSidebarOpen);
        const directMessage = useSelector(({chatApp}) => chatApp.directMessage);

        const classes = useStyles(props);
        const selectedContact = contacts.find(_contact => (_contact.id === selectedContactId));

        useEffect(() => {
            dispatch(Actions.getUserData());
            dispatch(Actions.getContacts());
            dispatch(Actions.directMessage({message: "123 testando...", to: "Carlos"}));
        }, [dispatch]);

        useEffect(() => {
            console.log(`Mensagem recebido: ${directMessage} TAMO NO APP`);
        }, [directMessage])

        return (
            <div className={clsx(classes.root)}>

                <div className={clsx(classes.contentCardWrapper, 'container')}>

                    <div className={classes.contentCard}>

                        <Hidden mdUp>
                            <Drawer
                                className="h-full absolute z-20"
                                variant="temporary"
                                anchor="left"
                                open={mobileChatsSidebarOpen}
                                onClose={() => dispatch(Actions.closeMobileChatsSidebar())}
                                classes={{
                                    paper: clsx(classes.drawerPaper, "absolute left-0")
                                }}
                                style={{position: 'absolute'}}
                                ModalProps={{
                                    keepMounted  : true,
                                    disablePortal: true,
                                    BackdropProps: {
                                        classes: {
                                            root: "absolute"
                                        }
                                    }
                                }}
                            >
                                <ChatsSidebar/>
                            </Drawer>
                        </Hidden>
                        <Hidden smDown>
                            <Drawer
                                className="h-full z-20"
                                variant="permanent"
                                open
                                classes={{
                                    paper: classes.drawerPaper
                                }}
                            >
                                <ChatsSidebar/>
                            </Drawer>
                        </Hidden>
                        <Drawer
                            className="h-full absolute z-30"
                            variant="temporary"
                            anchor="left"
                            open={userSidebarOpen}
                            onClose={() => dispatch(Actions.closeUserSidebar())}
                            classes={{
                                paper: clsx(classes.drawerPaper, "absolute left-0")
                            }}
                            style={{position: 'absolute'}}
                            ModalProps={{
                                keepMounted  : false,
                                disablePortal: true,
                                BackdropProps: {
                                    classes: {
                                        root: "absolute"
                                    }
                                }
                            }}
                        >
                            <UserSidebar/>
                        </Drawer>

                        <main className={clsx(classes.contentWrapper, "z-10")}>
                            {!chat ?
                                (
                                    <>
                                        <AppBar position="static" elevation={1}>
                                            <Toolbar className="px-16"/>
                                        </AppBar>
                                        <div className="flex flex-col flex-1 items-center justify-center p-24">
                                            <Paper className="rounded-full p-48">
                                                <Icon className="block text-64" color="secondary">chat</Icon>
                                            </Paper>
                                            <Typography variant="h6" className="my-24">{t("Chat")}</Typography>
                                            <Typography className="hidden md:flex px-16 pb-24 mt-24 text-center" color="textSecondary">
                                                {t("Select a contact to start a conversation!")}
                                            </Typography>
                                            <Button variant="outlined" color="primary" className="flex md:hidden normal-case" onClick={() => dispatch(Actions.openMobileChatsSidebar())}>
                                                {t("Select a contact to start a conversation!")}
                                            </Button>                                        
                                        </div>
                                    </>
                                ) : (
                                    <>
                                        <AppBar position="static" elevation={1}>
                                            <Toolbar className="px-16">
                                                <IconButton
                                                    color="inherit"
                                                    aria-label="Open drawer"
                                                    onClick={() => dispatch(Actions.openMobileChatsSidebar())}
                                                    className="flex md:hidden"
                                                >
                                                    <Icon>chat</Icon>
                                                </IconButton>
                                                <div className="flex items-center cursor-pointer" onClick={() => dispatch(Actions.openContactSidebar())}>
                                                    <div className="relative ml-8 mr-12">
                                                        <div className="absolute right-0 bottom-0 -m-4 z-10">
                                                            <StatusIcon status={selectedContact.status}/>
                                                        </div>

                                                        <Avatar src={selectedContact.avatar} alt={selectedContact.name}>
                                                            {!selectedContact.avatar || selectedContact.avatar === '' ? selectedContact.name[0] : ''}
                                                        </Avatar>
                                                    </div>
                                                    <Typography color="inherit" className="text-18 font-600">{selectedContact.name}</Typography>
                                                </div>
                                            </Toolbar>
                                        </AppBar>

                                        <div className={classes.content}>
                                            <Chat className="flex flex-1 z-10"/>
                                        </div>
                                    </>
                                )
                            }
                        </main>

                        <Drawer
                            className="h-full absolute z-30"
                            variant="temporary"
                            anchor="right"
                            open={contactSidebarOpen}
                            onClose={() => dispatch(Actions.closeContactSidebar())}
                            classes={{
                                paper: clsx(classes.drawerPaper, "absolute right-0")
                            }}
                            style={{position: 'absolute'}}
                            ModalProps={{
                                keepMounted  : true,
                                disablePortal: true,
                                BackdropProps: {
                                    classes: {
                                        root: "absolute"
                                    }
                                }
                            }}
                        >
                            <ContactSidebar/>
                        </Drawer>
                    </div>
                </div>
            </div>
        );
    }

    export default withReducer('chatApp', reducer)(ChatApp);

Codesandbox.io

https://codesandbox.io/s/signalr-core-and-redux-v7gp4

2 个答案:

答案 0 :(得分:1)

您正在调用动作创建者,但实际上并没有调度它在中间件中生成的动作,因此它只是创建了一个不执行任何操作的对象。

-    export default function signalRMiddleware() { 
+    export default function signalRMiddleware(api) { 

            connection.on('ReceiveMessage', data => {
-                Actions.receiveSocketMessage(data)
+                api.dispatch(Actions.receiveSocketMessage(data))
            })

            return  next => action => {

            switch (action.type) {

                case Actions.DIRECT_MESSAGE: 
                    connection.invoke('DirectMessage', action.payload);
            }

            return next(action);
        }
    }

答案 1 :(得分:0)

解决方案

SignalRMiddleware

import { HubConnectionBuilder, LogLevel } from '@microsoft/signalr';
import * as Actions from '../../main/apps/chat/store/actions';

const connection = new HubConnectionBuilder().withUrl("https://localhost:5001/chatHub")
                                        .configureLogging(LogLevel.Information)
                                        .withAutomaticReconnect()
                                        .build();

connection.start();

export default function signalRMiddleware(api) { 
    
    
    connection.on('ReceiveMessage', data => {
        console.log(`cheguei no middleware - msg: ${data}`);
            api.dispatch({type: Actions.RECEIVE_MESSAGE, payload: data});
        })
        
        return  next => action => {

        switch (action.type) {

            case Actions.DIRECT_MESSAGE: 
            {
                console.log(`enviando msg: ${action.payload.message}`);
                connection.invoke('DirectMessage', action.payload);
            }
        }
        
        return next(action);
    }
}

创建商店

import * as reduxModule from 'redux';
import {applyMiddleware, compose, createStore} from 'redux';
import createReducer from './reducers';
import signalRMiddleware from './middlewares/signalRMiddleware';
import thunk from 'redux-thunk';

/*
Fix for Firefox redux dev tools extension
https://github.com/zalmoxisus/redux-devtools-instrument/pull/19#issuecomment-400637274
 */
reduxModule.__DO_NOT_USE__ActionTypes.REPLACE = '@@redux/INIT';

const composeEnhancers =
    process.env.NODE_ENV !== 'production' &&
    typeof window === 'object' &&
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
        window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
            // Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
        }) : compose;

const enhancer = composeEnhancers(
    applyMiddleware(
        thunk,
        signalRMiddleware
        )
    // other store enhancers if any
);

const store = createStore(createReducer(), enhancer);

store.asyncReducers = {};

export const injectReducer = (key, reducer) => {
    if ( store.asyncReducers[key] )
    {
        return;
    }
    store.asyncReducers[key] = reducer;
    store.replaceReducer(createReducer(store.asyncReducers));
    return store;
};

export default store;