React Native:由于内存泄漏无法执行更新

React Native: Can't perform update because of memory leak

我正在测试连接了 2 个 android 模拟器的 RN (0.59) 聊天应用程序(gifted chat 0.7.3)。一个客户端上有关于内存泄漏的警告消息:

'Chat message URL : ', 'http://192.168.1.3:3000/api/messages/new'
06-14 14:09:56.628 12503 12552 I ReactNativeJS: 'message appended : ', [ { text: 'hi',
06-14 14:09:56.628 12503 12552 I ReactNativeJS:     user: { _id: 22, name: 'jc', avatar: undefined },
06-14 14:09:56.628 12503 12552 I ReactNativeJS:     createdAt: Fri Jun 14 2019 14:09:55 GMT-0700 (PDT),
06-14 14:09:56.628 12503 12552 I ReactNativeJS:     _id: '54e22cf0-0176-44da-9257-a904840a0d28' } ]
06-14 14:10:12.504 12503 12552 I ReactNativeJS: 'Received server message ', 'Hi did you hear me?'
06-14 14:10:12.510 12503 12552 E ReactNativeJS: 'Warning: Can\'t perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in %s.%s', 'the componentWillUnmount method', '\n    in Chat (at App.js:51)\n    in ChatWithSocket (at SceneView.js:9)\n    in SceneView (at StackViewLayout.js:786)\n    in RCTView (at View.js:45)\n    in View (at StackViewLayout.js:785)\n    in RCTView (at View.js:45)\n    in View (at StackViewLayout.js:784)\n    in RCTView (at View.js:45)\n    in View (at createAnimatedComponent.js:151)\n    in AnimatedComponent (at StackViewCard.js:69)\n    in RCTView (at View.js:45)\n    in View (at createAnimatedComponent.js:151)\n    in AnimatedComponent (at screens.native.js:59)\n    in Screen (at StackViewCard.js:57)\n    in Card (at createPointerEventsContainer.js:27)\n    in Container (at StackViewLayout.js:862)'

应用在componentDidMount提交消息检索请求后弹出警告。我不知道警告来自哪里。

这里是 Chat.js:

import React, { Component} from 'react';
import {View, StyleSheet,  AppRegistry } from 'react-native';
import { GiftedChat } from 'react-native-gifted-chat';
import DeviceInfo from 'react-native-device-info';
import GLOBAL from '../../lib/global';
import helper from "../../lib/helper";

export default class Chat extends React.Component {
    static navigationOptions = {
      title:  'Chat'
    }; 

    constructor(props) {
      super(props);
      console.log("Props socket id in chat : ", this.props.socket.id);
      console.log("Props in chat : ", this.props);
      this._onSend = this._onSend.bind(this);

      this.state = {
        messages: []
      };
    };

    async _onSend(messages = []) {
      console.log("socket.io in chat _onSend : ", this.props.socket.id);
      this.props.socket.emit("status", {msg: messages[0].text});
      //save message on backend
      const result = await helper.getJwtToken();
      console.log("secure store : ", result);
      if (!result) this.props.navigation.navigate("Event");
      let obj = JSON.stringify({
        _device_id: DeviceInfo.getUniqueID(),
        sender_id: this.props.navigation.state.params.user.id,
        socket_id: this.props.socket.id,
        data: {
          msg_body:messages[0].text,
          upload_content_info: ""
        },
        event_id: this.props.navigation.state.params.eventId,
      });
      try {
        let url = `${GLOBAL.BASE_URL}/api/messages/new`;
        console.log("Chat message URL : ", url);
        let res = await fetch(url, {
          method: "POST",
           headers: {
            'Accept': 'application/json, text/plain',
            'Content-Type': 'application/json',   
            "x-auth-token": result.password,
            "x-auth-secret" : result.username,
          },
          body: obj,
        });
      }
      catch (err) {
        console.log("Error in saving message : ", err.message);
      };
      this.setState(previousState => ({
        messages: GiftedChat.append(previousState.messages, messages),
      }))
      console.log("message appended : ", messages);
    }

    formatMessages(messages) {
      let obj = [], r = {};

      for (i = 0; i < messages.length; i++) {
         r = {
            _id: '',
            text: '',
            createdAt : '',
            user: {
              _id: '',
              name: '',
              avatar: ''
            }
          };
         r._id = messages[i].id;
         r.text = messages[i].data.msg_body;
         r.createdAt = messages[i].createdAt;
         r.user._id = messages[i].sender_id;
         r.user.avatar = messages[i].user.user_data.avatar;
         r.user.name = messages[i].user.name;
         console.log("r in chat : ", r);
         obj.push(r);
      }
      console.log("formated messages : ", obj);
      return obj;
    };

    async componentDidUpdate() {

    };

    async componentDidMount() {
      console.log(" Chat this.props.socket.id in did mount : ", this.props.socket.id);
      const result = await helper.getJwtToken();
      console.log("secure store : ", result);
      if (!result) this.props.navigation.navigate("Event");
      //handle socket.io input
      this.props.socket.on("event message", (msg) => {
        console.log("Received server message ", msg.data.msg_body); //<<==output
        let r = {
          _id: msg.id,
          text: msg.data.msg_body,
          createdAt : msg.createdAt,
          user: {
            _id: msg.sender_id,
            name: msg.user_name,
            avatar: msg.user_avatar,
          }
        };
        this.setState(previousState => ({
          messages: GiftedChat.append(previousState.messages, r), //<<===problem here???
        }))
      });

      try {
        let url = `${GLOBAL.BASE_URL}/api/messages/e?_device_id=${encodeURIComponent(DeviceInfo.getUniqueID())}&event_id=${encodeURIComponent(this.props.navigation.state.params.eventId)}`;
        console.log("Chat message URL : ", url);
        let res = await fetch(url, {
          method: "GET",
           headers: {
            'Accept': 'application/json, text/plain',
            'Content-Type': 'application/json',   
            "x-auth-token": result.password,
            "x-auth-secret" : result.username,
          }
        });
        //
        secret = res.headers.get("x-auth-secret");
        token = res.headers.get("x-auth-token");
        if (secret && token) await helper.updateJwtToken(secret, token);
        res = await res.json();
        console.log(res);
        let formated_msgs = this.formatMessages(res);
        this.setState({
            messages: formated_msgs,
          })
        console.log("Messages : ", this.state.messages);

      }
      catch (e) {
        console.log(e);
      };
    }

    render() {
      return (
          <GiftedChat 
            messages={this.state.messages}
            onSend={messages => this._onSend(messages)}
            user={{_id: this.props.navigation.state.params.user.id,
                   name: this.props.navigation.state.params.user.name,
                   avatar: this.props.navigation.state.params.user.user_data.avatar}}
          /> 
      );
    }

}


AppRegistry.registerComponent('Chat', () => 'Chat');

该错误是Chat卸载后的设置状态导致的。修复的方法是在构造的时候加一个this._isMounted,在componentWillUnmount中设置false。检查 _isMounted before set 状态。这是修改后的 Chat.js 代码:

import React, { Component} from 'react';
import {View, StyleSheet,  AppRegistry } from 'react-native';
import { GiftedChat } from 'react-native-gifted-chat';
//import io from 'socket.io-client';
import DeviceInfo from 'react-native-device-info';
import GLOBAL from '../../lib/global';
import helper from "../../lib/helper";
//import secureStore from "../../lib/securestore";

export default class Chat extends React.Component {
    static navigationOptions = {
      title:  'Chat'
    }; 

    constructor(props) {
      super(props);
      this._isMounted = true;  
      console.log("Props socket id in chat : ", this.props.socket.id);
      console.log("myRef in chat constructor : ", this.myRef);
      this._onSend = this._onSend.bind(this);

     ....
    componentWillUnmount() {
      this._isMounted = false;
      this.props.socket.removeListener("event message", () => {
        console.log("event message removed in Chat");
      })
    };

    async componentDidMount() {
      console.log(" Chat this.props.socket.id in did mount : ", this.props.socket.id);
      const result = await helper.getJwtToken();
      console.log("secure store : ", result);
      if (!result) this.props.navigation.navigate("Event");
      //handle socket.io input
      this.props.socket.on("event message", (msg) => {
        console.log("Received server message ", msg.data.msg_body);
        let r = {
          _id: msg.id,
          text: msg.data.msg_body,
          createdAt : msg.createdAt,
          user: {
            _id: msg.sender_id,
            name: msg.user_name,
            avatar: msg.user_avatar,
          }
        };
        console.log("_isMounted : ", this._isMounted);
        if(this._isMounted) this.setState(previousState => ({
          messages: GiftedChat.append(previousState.messages, r),
        }))
      });

      try {
        let url = `${GLOBAL.BASE_URL}/api/messages/e?_device_id=${encodeURIComponent(DeviceInfo.getUniqueID())}&event_id=${encodeURIComponent(this.props.navigation.state.params.eventId)}&socket_id=${encodeURIComponent(this.props.socket.id)}`;
        console.log("Chat message URL : ", url);
        let res = await fetch(url, {
          method: "GET",
           headers: {
            'Accept': 'application/json, text/plain',
            'Content-Type': 'application/json',   
            "x-auth-token": result.password,
            "x-auth-secret" : result.username,
          }
        });
        //
        secret = res.headers.get("x-auth-secret");
        token = res.headers.get("x-auth-token");
        if (secret && token) await helper.updateJwtToken(secret, token);
        res = await res.json();
        console.log(res);
        let formated_msgs = this.formatMessages(res);
        this.setState({
            messages: formated_msgs,
          })
        console.log("Messages : ", this.state.messages);

      }
      catch (e) {
        console.log(e);
      };
    }

    render() {
      return (
          <GiftedChat 
            messages={this.state.messages}
            onSend={messages => this._onSend(messages)}
            user={{_id: this.props.navigation.state.params.user.id,
                   name: this.props.navigation.state.params.user.name,
                   avatar: this.props.navigation.state.params.user.user_data.avatar}}
          /> 
      );
    }

}

AppRegistry.registerComponent('Chat', () => 'Chat');