React-Native:警告:无法对卸载的组件执行 React 状态更新 [英] React-Native: Warning: Can't perform a React state update on an unmounted component

查看:42
本文介绍了React-Native:警告:无法对卸载的组件执行 React 状态更新的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我尝试从一个屏幕转换到另一个屏幕时收到以下错误消息:

I am getting the following error message when I try to transition from one screen to another:

这种情况发生在游戏应用中,其中有多部手机参与游戏并且根据它们在游戏中的角色以及该手机是托管游戏还是访客而具有不同的屏幕.

This is happening in a game app where multiple phones are involved in the game and have different screens depending on their role in the game and depending if that phone is hosting the game or is a guest.

下图显示了当我尝试进入下一个屏幕(在左侧手机上)时的错误消息.左侧手机的屏幕应该与右侧的相同,但没有下一轮"和结束游戏"按钮.但是我收到了一个完全不同的屏幕,并显示错误消息:

The following image shows this error message when I am trying to reach the next screen (on the left phone). The screen on the left phone is supposed to be the same as the one on the right, but without the buttons "Next Round" and "End Game". But I am getting a totally different screen with the error message:

这里是前一个屏幕的代码,应该将手机导航到这个分数"屏幕:

Here is the code for the previous screen that is supposed to navigate the phones to this "score" screen:

import React, {Component} from 'react';
import {AppRegistry, View, Text, ScrollView, StyleSheet} from 'react-native';
import {CardFlip1} from '../components/CardFlip1';
import {CardFlip2} from '../components/CardFlip2';
import colors from '../config/colors';
import {PrimaryButton} from '../components/PrimaryButton';
import AsyncStorage from '@react-native-community/async-storage';
import Orientation from 'react-native-orientation-locker';
window.navigator.userAgent = 'react-native';
import io from 'socket.io-client/dist/socket.io';

class judge_screen extends Component {
    constructor (props) {
        super(props);
        this.state = {
            game_round: '',
            player1: '',
            player2: '',
            label1: '',
            label2: '',
            current_user: ''
        }
    }

    componentWillMount = () => {
        this.getActives();
    }

    componentDidMount() {
        Orientation.lockToLandscape();
        this.socket = io("socket address is here", {
            jsonp: false
        });
    }

    getActives = async () => {
        let user = await AsyncStorage.getItem('email');
        let player_1 = await AsyncStorage.getItem('Player1');
        let player_2 = await AsyncStorage.getItem('Player2');
        let round = await AsyncStorage.getItem('Round');

        this.setState({game_round: round});
        this.setState({player1: player_1});
        this.setState({player2: player_2});
        var label_start = "Choose ";
        var label_end = "'s fate";
        var player_1_name = this.state.player1;
        var player_2_name = this.state.player2;
        var label1_str = label_start.concat(player_1_name, label_end);
        this.setState({label1: label1_str});
        var label2_str = label_start.concat(player_2_name, label_end);
        this.setState({label2: label2_str});
    }

    player1Win = async () => {
        let host = await AsyncStorage.getItem('host');
        if (host == 'yes') {
            let user = await AsyncStorage.getItem('email');
            this.setState({current_user: user});
        } else {
            let user = await AsyncStorage.getItem('users_id');
            this.setState({current_user: user});
        }
        var user_fix = this.state.current_user;
        let player_name = await AsyncStorage.getItem('Player1');
        AsyncStorage.setItem('Winner', player_name);

        fetch('fetch address is here', {
            method: 'POST',
            headers: {
            'Accept': 'application/json',
            'Content-Type': 'application.json',
            },
            body: JSON.stringify({
                email: user_fix,
                Name: player_name,
                Host: host
            })
        }).then((response) => response.json())
        .then((responseJson) => {
            if (host == 'yes') {
                this.socket.emit('end_round', 'end');
                this.props.navigation.navigate('end_round_host_screen');
            } else {
            // This is the navigation to the screen getting the error:
                this.socket.emit('end_round', 'end');
                this.props.navigation.navigate('end_round_guest_screen');
            }
        }).catch((error) => {
            console.error(error);
        });
    }

    player2Win = async () => {
        let host = await AsyncStorage.getItem('host');
        if (host == 'yes') {
            let user = await AsyncStorage.getItem('email');
            this.setState({current_user: user});
        } else {
            let user = await AsyncStorage.getItem('users_id');
            this.setState({current_user: user});
        }
        var user_fix = this.state.current_user;
        let player_name = await AsyncStorage.getItem('Player2');
        AsyncStorage.setItem('Winner', player_name);

        fetch('fetch address is here', {
            method: 'POST',
            headers: {
            'Accept': 'application/json',
            'Content-Type': 'application.json',
            },
            body: JSON.stringify({
                email: user_fix,
                Name: player_name,
                Host: host
            })
        }).then((response) => response.json())
        .then((responseJson) => {
            if (host == 'yes') {
                this.socket.emit('end_round', 'end');
                this.props.navigation.navigate('end_round_host_screen');
            } else {
            // This is the navigation to the screen getting the error:
                this.socket.emit('end_round', 'end');
                this.props.navigation.navigate('end_round_guest_screen');
            }
        }).catch((error) => {
            console.error(error);
        });
    }

    render() {
        return (
            <ScrollView>
                <View style={{flexDirection: 'row'}}>
                    <View style={styles.container}>
                        <Text style={[styles.text]}>
                            {this.state.player1}
                        </Text>
                        <CardFlip1 />
                        <PrimaryButton
                            onPress={() => this.player1Win()}
                            label={this.state.label1}
                        >
                        </PrimaryButton>
                    </View>
                    <View style={styles.container}>
                        <Text style={[styles.text]}>
                            {this.state.player2}
                        </Text>
                        <CardFlip2 />
                        <PrimaryButton
                            onPress={() => this.player2Win()}
                            label={this.state.label2}
                        >
                        </PrimaryButton>
                    </View>
                </View>
            </ScrollView>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        alignItems: 'center',
        backgroundColor: colors.backgroundColor,
        margin: 10,
        paddingBottom: 5,
        borderWidth: 1,
        borderColor: colors.borderColor,
    },
    text: {
        fontSize: 18,
        color: colors.primaryText,
        marginTop: 10,
    },
    textPadding: {
        paddingBottom: 10,
    },
    headingText: {
        fontSize: 24,
        fontWeight: '500',
        color: colors.primaryText,
        margin: 10,
    },
    textMarginHorizontal: {
        marginHorizontal: 10,
    },
})

export default judge_screen;

这是我试图导航到的end_round_guest_screen"的代码:

Here is the code for the "end_round_guest_screen" that I am trying to navigate to:

import React, {Component} from 'react';
import {View, Text, ScrollView, StyleSheet} from 'react-native';
import colors from '../config/colors';
import {PrimaryButton} from '../components/PrimaryButton';
import {ScoreBoardGuest} from '../components/ScoreBoardGuest';
import AsyncStorage from '@react-native-community/async-storage';
import Orientation from 'react-native-orientation-locker';
window.navigator.userAgent = 'react-native';
import io from 'socket.io-client/dist/socket.io';

class end_round_guest_screen extends Component {
    constructor (props) {
        super(props);
        this.state = {
            game_round: '',
            winner: ''
        }
    }

    componentWillMount = () => {
        this.getActives();
        this.getWinner();
    }

    componentDidMount() {
        Orientation.unlockAllOrientations();
        this.socket = io("socket address is here", {
            jsonp: false
        });
        this.socket.on('next_round', () => this.nextRound());
        this.socket.on('end_game', () => this.endGame());
    }

    getActives = async () => {
        let round = await AsyncStorage.getItem('Round');
        this.setState({game_round: round});
    }

    getWinner = async () => {
        let user = await AsyncStorage.getItem('users_id');
        //let host = await AsyncStorage.getItem('host');

        fetch('fetch address is here', {
            method: 'POST',
            headers: {
            'Accept': 'application/json',
            'Content-Type': 'application.json',
            },
            body: JSON.stringify({
                email: user
            })
        }).then((response) => response.json())
        .then((responseJson) => {
            this.setState({winner: responseJson});
        }).catch((error) => {
            console.error(error);
        });
    }

    nextRound = () => {
        this.props.navigation.navigate('round_start_guest_screen');
    }

    endGame = () => {
        this.props.navigation.navigate('end_game_guest_screen');
    }

    render() {
        return (
            <ScrollView>
                <View style={{alignItems: 'center'}}>
                    <Text style={styles.headingText}>
                        Round {this.state.game_round}
                    </Text>
                    <Text style={styles.text}>
                        {this.state.winner} wins this round!
                    </Text>
                </View>
                <View style={styles.container}>
                    <ScoreBoardGuest />
                </View>
            </ScrollView>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        alignItems: 'center',
        backgroundColor: colors.backgroundColor,
        margin: 10,
        paddingVertical: 5,
        borderWidth: 1,
        borderColor: colors.borderColor,
    },
    text: {
        fontSize: 18,
        color: colors.primaryText,
        marginTop: 10,
    },
    headingText: {
        fontSize: 24,
        fontWeight: '500',
        color: colors.primaryText,
        margin: 10,
    },
})

export default end_round_guest_screen;

end_round_guest_screen"在没有加载任何状态的情况下显示一两秒钟,然后转到带有错误消息的回牌"屏幕.

The "end_round_guest_screen" shows for a second or 2 without any states loaded and then goes to that "Card Back" screen with the error message.

推荐答案

堆栈跟踪显示错误发生在 end_round_guest_screen 内部.根据您的描述,它在导航到Card Back"并出现错误之前显示了 2 秒,我假设它发生在 fetch 中的 this.setState({winner: responseJson}) 行上打回来.

The stacktrace shows that the error is happening inside the end_round_guest_screen. Based on your description that it is displayed for 2 seconds before navigating to "Card Back" with an error, I assume that it's happening on the line this.setState({winner: responseJson}) inside the fetch callback.

如果获取请求仍在等待响应,则可能会发生这种情况,然后调用 nextRound 或 endGame 处理程序,从而触发导航,从而触发卸载.到 fetch 获取数据并即将调用 setState 时,该组件已不再挂载.

This could happen if the fetch request is still waiting for the response, then either the nextRound or endGame handler got called, which triggered the navigation, and therefore the unmount. By the time fetch got the data, and is about to call setState, the component is no longer mounted.

一般有两种方法可以解决这个问题.

There are two ways to solve this generally.

1.) (Anti-pattern) 使用 componentDidMount/componentWillUnmount 回调跟踪组件是否仍然挂载,然后在调用 setState 之前执行 isMounted 检查.

componentDidMount() {
    this.isMounted = false
}

componentWillUnmount() {
    this.isMounted = false
}

getWinner = async () => {
    //inside fetch
    if (this.isMounted) {
        this.setState({winner: responseJson})
    }
}

2.)(推荐)取消 componentWillUnmount 回调中的任何挂起的网络请求.

例如,您可以使用 AbortController 取消 fetch 请求.有关示例代码,请参阅 https://stackoverflow.com/a/53435967/803865.

For example, you can use an AbortController to cancel fetch request. See https://stackoverflow.com/a/53435967/803865 for sample code.

这篇关于React-Native:警告:无法对卸载的组件执行 React 状态更新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆