我如何使用react组件功能维护状态和“ this”

时间:2019-03-19 20:59:16

标签: reactjs socket.io ibm-watson watson-assistant

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

所以当前的流是这样的:

我从组件发出一条消息,该消息到达我的服务器,然后到达Watson API。 API将响应发送到我的服务器,然后服务器将响应发送到组件。

因此,第一个消息创建了与socket.io的连接,然后第二个socket.io事件被捕获在同一连接上。

是否有更好的方法来建立与socket.io的连接,并同时处理发射和“消息传递”部分?感谢您提前任何提示。我仍然是新来的反应者,所以您发现我应该做不同的事情都是有帮助的!

...
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);

3 个答案:

答案 0 :(得分:3)

由于您已经在使用redux,所以最好让socket.io为您调度redux操作,因此可以使用与article描述相同的redux saga,也可以不使用saga来使用此实现,但是您需要使用{ {3}}中间件:

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} />
}

答案 1 :(得分:3)

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

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

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

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

// socketHelper.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/socketHelper.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);
        })  
    }

    ...
}

React docs

  

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

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


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

_onMessageWasSent(data) {
    this.setState(previousState => ({
        messageList: [
            {
                author: 'them',
                type: 'text',
                data: data,
            }, 
            ...previousState.messageList,
        ]
    }))
}

答案 2 :(得分: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([]);
    ...