如何使用 React 组件函数维护状态和 "this"

How can I maintain state and "this" with react component functions

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

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

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

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

是否有更好的方法来设置与 socket.io 的连接并处理其中的发射和 "on gmessage" 部分?感谢您提前提供 any 提示。我对反应还很陌生,所以你看到的任何我应该做的不同的事情都是有帮助的!!

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

因为你已经在使用 redux,所以最好让 socket.io 为你调度 redux 动作,你可以使用与这个 article describes or you can use this implementation without saga but you need to have redux-thunk 中间件相同的 redux saga :

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

根据您的应用程序,我认为 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([]);
    ...

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

    ...
}

React docs

If you need to load data from a remote endpoint, this is a good place to instantiate the network request.

componentDidMount 只会 运行 一次,所以你的监听器不会被重新定义多次。


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

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