0

目前,当 socket.io 从我的服务器发出“gmessage”并且我的组件中的套接字捕获它时,我的整个状态都被替换了。

所以现在的流程是这样的:

我从组件发出一条消息,它发送到我的服务器,然后发送到 watson API。API 向我的服务器发送响应,服务器将响应发送给组件。

所以第一条消息创建了到 socket.io 的连接,然后第二个 socket.io 事件在同一个连接上被捕获。

有没有更好的方法来建立与 socket.io 的连接并处理这部分的发射和“on gmessage”部分?感谢您提前提供任何提示。我还是新手,所以你看到我应该做的任何不同都是有帮助的!

...
import {Launcher} from 'react-chat-window'
import io from 'socket.io-client';

import { getUser } from '../actions/userActions';

class Workshop extends Component {

  constructor() {
      super();
      this.state = {
        messageList: []
      };
    }

  _onMessageWasSent(message) {
    this.setState({
      messageList: [message, ...this.state.messageList]
    })

    var socket = io.connect('http://localhost:5000');

    socket.emit('message', { message });

    var myThis = this
    var myState = this.state

    socket.on('gmesssage', function (data) {
      console.log(data);
      myThis.setState({
        messageList: [{
          author: 'them',
          type: 'text',
          data:  data
        }, ...myState.messageList]
      })
    })
  }

  render() {
    return (
      <Grid container className="DashboardPage" justify="center">
        <Grid item xs={12}>
          <div>Welcome to your Workshop</div>
          <TeamsTaskLists />
        </Grid>

        <Launcher
          agentProfile={{
            teamName: 'Geppetto',
            imageUrl: 'https://geppetto.ai/wp-content/uploads/2019/03/geppetto-chat-avi.png'
          }}
          onMessageWasSent={this._onMessageWasSent.bind(this)}
          messageList={this.state.messageList}
          showEmoji
        />

      </Grid>
    );
  }
}

const mapStatetoProps = state => ({
  user: state.user
});

export default connect(
  mapStatetoProps,
  { getUser }
)(Workshop);
4

3 回答 3

4

Fareed 提供了一个可行的 Redux 解决方案并建议对非 Redux 方法进行改进,因此我想解决您当前代码中的问题:

  1. 您正在重新初始化套接字变量并在每次收到新消息时创建它的侦听器,而不是在单独的文件中配置和初始化套接字对象一次,然后由您的 Workshop 组件和可能在您的项目中的其他组件使用它。

  2. 您通过在_onMessageWasSent中调用setState({ messages: [...] }) 两次来附加新收到的消息。

要解决第一个问题,您可以创建一个单独的文件并将与套接字相关的导入和初始化移动到其中,如下所示:

// socket-helper.js

import io from 'socket.io-client';

export const socket = io.connect('http://localhost:5000');

请记住,这是一个最小配置,您可以在导出之前尽可能多地调整您的套接字对象,只要 socket.io API 允许您这样做。


然后,在 Workshop 组件的componentDidMount中订阅此套接字对象,例如:

import { socket } from 'path/to/socket-helper.js';

class Workshop extends Component {
    constructor(props) {
        super(props);
        ...
    }

    componentDidMount() {
        // we subscribe to the imported socket object just once
        // by using arrow functions, no need to assign this to myThis
        socket.on('gmesssage', (data) => {
              this._onMessageWasSent(data);
        })  
    }

    ...
}

反应文档

如果您需要从远程端点加载数据,这是实例化网络请求的好地方。

componentDidMount只会运行一次,因此您的侦听器不会被重新定义多次。


为了解决第二个问题,_onMessageWasSent处理函数应该只接收新消息并使用 setState 将其附加到以前的消息中,如下所示:

_onMessageWasSent(data) {
    this.setState(previousState => ({
        messageList: [
            {
                author: 'them',
                type: 'text',
                data: data,
            }, 
            ...previousState.messageList,
        ]
    }))
}
于 2019-03-19T22:13:11.890 回答
3

由于您已经在使用 redux,最好让 socket.io 为您调度 redux 操作,您可以使用与本文描述的相同的 redux saga,或者您可以在没有 saga 的情况下使用此实现,但您需要有redux-thunk中间件:

1-创建一个文件让我们说lib/socket.js

然后在这个文件中创建socket.io客户端让我们调用它socket并创建initSocketIO函数,您可以在其中触发任何 redux 操作以响应 socket.io:

 import socket_io from "socket.io-client"; 
 // our socket.io client
 export const socket = socket_io("http://localhost:5000");

 /**
 * init socket.io redux action 
 * @return {Function}
 */
export const initSocketIO = () => (dispatch, getState) => {
  // connect
  socket.on('connect', () => dispatch(appActions.socketConnected()));

  // disconnect
  socket.on('disconnect', () => dispatch(appActions.socketDisconnected()));

  // message
  socket.on('message', message => {
      dispatch(conversationActions.addOrUpdateMessage(message.conversationId, message.id, message));
      socket.emit("message-read", {conversationId: message.conversationId});
  });

  // message
  socket.on('notifications', ({totalUnread, notification}) => {
    dispatch(recentActions.addOrUpdateNotification(notification));
    dispatch(recentActions.setTotalUnreadNotifications(totalUnread));
  });
};

2-然后在您的App组件内部调用此操作,一旦您App安装:

import React, {Component} from "react";
import {initSocketIO} from "./lib/socket.js";

export class App extends Component {
   constructor(props) {
    super(props);
    this.store = configureStore();
  }
  componentDidMount() {
    // init socket io
    this.store.dispatch(initSocketIO());
  }
  // ... whatever
}

3-现在您还可以使用以下命令从任何组件触发任何 socket.io 操作:

  import React, {Component} from "react";
  import {socket} from "./lib/socket.js"

  MyComponent extends Components {
     myMethod = () => socket.emit("message",{text: "message"});
  }

或从任何使用 thunk 的 redux 操作,例如:

  export const sendMessageAction = (text) => dispatch => {
     socket.emit("my message",{text});
     dispatch({type: "NEW_MESSAGE_SENT",payload:{text}});
  }

笔记:

1-这将完美地工作,但我仍然更喜欢 redux-saga 方法来管理 API 和 socket.io 等任何副作用。

2-在您的组件中,您不需要将组件方法绑定为this仅使用箭头函数,例如:

myMethod = () => {
  // you can use "this" here
}

render = (){
  return <Launcher onMessageWasSent={this.myMethod} />
}
于 2019-03-19T21:36:42.497 回答
1

根据您的应用程序,我认为 Redux 会为此大材小用。

我会在此处更改为 lambda 表达式,以避免必须保存此引用:

socket.on('gmesssage', data => {
  console.log(data);
  this.setState({
    messageList: [{
      author: 'them',
      type: 'text',
      data:  data
    }, ...this.messageList]
  })
})

您也可以考虑使用新的 React Hooks,例如将其转换为无状态组件并使用useState

const Workshop = () => {
    const [messageList, setMessageList] = useState([]);
    ...
于 2019-03-19T22:01:24.257 回答