我是使用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
答案 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;