如何通过反应组件功能来维护状态和“this”

问题描述 投票:0回答:3

目前,当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);
reactjs socket.io ibm-watson watson-assistant
3个回答
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,
        ]
    }))
}

3
投票

由于你已经在使用redux,最好为你做socket.io dispatch redux动作,你可以使用与article描述相同的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} />
}

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([]);
    ...
© www.soinside.com 2019 - 2024. All rights reserved.