【问题标题】:A better way to render this function component?渲染此功能组件的更好方法?
【发布时间】:2021-11-02 21:10:03
【问题描述】:

以下代码的目标是将数据从 firebase 实时数据库中提取到名为 popularMovies 的数组中。 (实时数据库中的数据量为 1000。)然后从该数组中选择一个随机索引,并将该索引处的电影数据显示在页面上。当用户单击点赞按钮时,该电影数据将存储在用户 id 下的 firestore 文档中,并选择另一个随机索引来显示新电影。

下面的代码大部分都有效,但我注意到 console.log("run!") 在页面加载和点击喜欢的按钮时执行 1000 次(正在读取的数据的大小)。我希望理解为什么会这样,因为它在读取数据的 forEach 循环之外。

此外,当单击按钮一次时,所有数据都会更新,但显示不会反映更改。但是,再次单击该按钮确实会随着显示再次更新数据(正如我所期望的第一次发生的那样)。并且此按钮仅在每次按下后更新显示。我也想知道是什么导致了这个错误。

import React, { useState, useEffect } from 'react';
import './App.css';
import firebase from 'firebase/compat/app';
import 'firebase/database';
import 'firebase/firestore';

function RateMovies() {

    const popularMoviesRef = firebase.database().ref("/popular_movies");
    const [popularMovies, setPopularMovies] = React.useState([]);
    var [render, setRender] = React.useState(1);

    console.log("run!");

    useEffect(() => {
        
        popularMoviesRef.once("value", function(snapshot){

            snapshot.forEach(function(childSnapshot){
                var key = childSnapshot.key;
                var data = childSnapshot.val();

                setPopularMovies(currMovies => [...currMovies, { 
                    key: key, 
                    movieTitle: data.movie_title, 
                    moviePoster: data.movie_poster, 
                    movieYear: data.movie_year,
                    movieGenre: data.movie_genre, 
                    movieRating: data.imdb_rating
                }])
            });
        });
    }, [render]);

    var index = Math.floor(Math.random() * (1000 + 1));
    var movie = popularMovies[index];

    const auth = firebase.auth();
    var db = firebase.firestore();
    var user_id = auth.currentUser.uid;

    function likedMovie() {

        db.collection('users').doc(user_id).update({
            "Rated.liked": firebase.firestore.FieldValue.arrayUnion({
                key: movie.key, 
                movieTitle: movie.movieTitle, 
                moviePoster: movie.moviePoster, 
                movieYear: movie.movieYear,
                movieGenre: movie.movieGenre, 
                movieRating: movie.movieRating,
             }),
        })
        .then(function(){
            console.log("Successfully updated!");
            setRender(render++);
        });

        index = Math.floor(Math.random() * (1000 + 1));
        movie = popularMovies[index];
    }


    return (
        <div>
            <p>Rate Movies Here</p>
            <div> {movie?.movieTitle} </div>
            <div> {movie?.movieYear} </div>
            <br/>
            <button onClick={likedMovie}> Like</button>
            <button> Disike</button>
            <br/>
            <br/>
            <img class="rate-image" src = {`${movie?.moviePoster}`} />
            
        </div>
    )
  
  } 

  export default RateMovies;

【问题讨论】:

标签: reactjs firebase render use-state


【解决方案1】:

您看到console.log() 1000 次的原因是因为您的forEach() 循环更新了RateMovies() 中定义的状态变量(popularMovies)。每次状态改变时,React 都会重新渲染你的组件。

不要一次添加一个新项目,而是在forEach 循环中创建一个新数组。这只会修改一次状态,这意味着组件应该只重新渲染一次。

试试这样的:

useEffect(() => {
        
        popularMoviesRef.once("value").then(function(snapshot){
            
            var newArray = []
    
            snapshot.forEach(function(childSnapshot){
                var key = childSnapshot.key;
                var data = childSnapshot.val();

                newArray.push({ 
                    key: key, 
                    movieTitle: data.movie_title, 
                    moviePoster: data.movie_poster, 
                    movieYear: data.movie_year,
                    movieGenre: data.movie_genre, 
                    movieRating: data.imdb_rating
                })
            });

            setPopularMovies([...popularMovies, ...newArray])

        }));
    }, [render]);

至于你的按钮没有重新呈现你想要的数据,我不能 100% 确定,但这可能与 JavaScript 处理承诺的方式有关。您的useEffect 中的popularMoviesRef.once 呼叫可能与您的db.collection('users').doc(user_id).update 同时发生。您可以通过将console.log() 添加到您的useEffect 函数来测试它,以尝试查看它是否在"Successfully Updated!" 之前记录。

请看这里:What is the order of execution in JavaScript promises?

【讨论】:

  • 我收到 snapshot.map 不是函数的错误
  • @Kris 你是对的,我忘了.once() 返回一个对象,而不是一个数组。必须使用forEach() 完成。我更新了我的答案以反映这一点。
  • 非常感谢!您对为什么 render++ 需要在页面更新之前执行两次有任何见解吗?
  • @Kris 我不能 100% 确定是什么原因造成的,但它可能与 JavaScript 如何在 promise 中处理 .then() 有关。我用一些可能有用的相关信息更新了我的答案。