SetState引起多次重新渲染

3
我正在创建一个聊天应用,我有一个用于显示所有消息的主屏幕组件。我试图确保每当用户发送或接收一条消息时,该组件就会渲染并更新Flatlist以显示最后发送的消息。我使用Firebase实时数据库来监听更新。 我在我的应用程序中使用函数式组件,但我认为我错误地使用了setState,因为它导致我的组件多次重新渲染。 我已经仔细检查了提问的问题,但似乎没有一个与我的问题相关。 这是我的代码。

const AllMessagesScreen = ({ navigation }) => {
    const uid = firebase.auth().currentUser.uid;

    const [state, setState] = useState({
        allMessages: []
    });

    const { allMessages } = state;

    const fsdb = firebase.firestore().collection('Users');
    const db = database().ref(`messages/${uid}`); //realtime database

    const get = async () => {

        try {
            await db.on('value', snapshot => {

                snapshot.forEach(item => {

                    const { key: _id } = item;
                    const { lastMessage } = item.val();

                    fsdb.doc(_id).get().then((doc => {
                        const otherUser = doc.data();
                        const newChat = {
                            id: _id,
                            name: otherUser.name,
                            picture: otherUser.pictures.thumbnailUrl,
                            lastMessage: lastMessage
                        };
                        setState(prevState => ({
                             allMessages: [...prevState.allMessages, newChat]
                        }));
                    })).catch((err) => {
                        console.log('caught', err)
                    });
                });
            });
        } catch (err) {
            console.log('error caught', err)
        };

    };

    useEffect(() => {
        get();

    }, []);

    return (
        <View style={styles.screen}>
            <FlatList
                data={allMessages}
                keyExtractor={item => item.id}
                renderItem={({ item }) => (
                    <View style={{ flex: 1 }}>
                        <MessageCard
                            id={item.id}
                            name={item.name}
                            image={item.picture}
                            lastMessageText={item.lastMessage.text}
                        />
                    </View>
                )}
            />

        </View>
    );

}

我也尝试过这样设置状态

setState({ allMessages: [...allMessages, newUser] });
``
The second way only shows the last rendered message and gets rid of the rest.
Any help is really appreciated
1个回答

2
您不应该在循环中更新状态,将setState从循环内取出,在循环后调用它。
每次state更新时,React都会重新渲染component,在这种情况下,React将在每次迭代后重新渲染component,这可能会导致意外的结果。
您还有其他一些问题需要解决。
  1. 您不应该像这样使用状态:const { allMessages } = state;,因为它始终会在每次渲染时给您一个空数组。需要时,请像这样使用: state.allMessages
  2. 每次渲染时都会获取database连接,这可能会导致内存泄漏。使用useRef来保存db连接。
  3. useEffect钩子(在组件挂载时)中初始化db连接和其他内容。
我已经更新了代码以解决上述问题(我已经将then块更改为await,因为您在代码中使用了async/await,并将forEach替换为for of)。
import React, { useState, useRef } from 'react';
const AllMessagesScreen = ({ navigation }) => {
    const [state, setState] = useState({
        allMessages: []
    });
    let fsdb = useRef();
    let db = useRef();
    useEffect(() => {
        const uid = firebase.auth().currentUser.uid;
        fsdb.current = firebase.firestore().collection('Users');
        db.current = database().ref(`messages/${uid}`); //realtime database
        fetchMessage();
    }, []);
    async function fetchMessage() {
        try {
            await db.current.on('value', snapshot => {
                let newChat = []
                for (const item of snapshot) {
                    const { key: _id } = item;
                    const { lastMessage } = item.val();
                    let doc = await fsdb.current.doc(_id).get();
                    const otherUser = doc.data();
                    newChat.push({
                        id: _id,
                        name: otherUser.name,
                        picture: otherUser.pictures.thumbnailUrl,
                        lastMessage: lastMessage
                    });
                }
                setState(prevState => ({
                    allMessages: [...prevState.allMessages, ...newChat]
                }));
            });
        } catch (err) {
            console.log('error caught', err)
        };
    };

    return (
        <View style={styles.screen}>
            <FlatList
                data={state.allMessages}
                keyExtractor={item => item.id}
                renderItem={({ item }) => (
                    <View style={{ flex: 1 }}>
                        <MessageCard
                            id={item.id}
                            name={item.name}
                            image={item.picture}
                            lastMessageText={item.lastMessage.text}
                        />
                    </View>
                )}
            />
        </View>
    );
}

谢谢Sohail。我已经尝试过了,但由于某种原因,allMessages从未得到更新,始终是一个空数组。你确定这是设置状态的正确方式吗? - abdi
谢谢@Sohail。我已经尝试过了,但现在出现了一个错误:TypeError: undefined不是一个函数(附近的“...tor”:“@@iterator”]();…') - abdi
抱歉让你感到困惑,这是一个警告,但我的FlatList没有被渲染。也没有行号。 - abdi
让我们在聊天中继续这个讨论 - Sohail Ashraf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接