React组件函数内console.log显示空数组问题求助
拖拽功能中函数内数组为空的问题解决
问题描述
函数内的console.log('in function', randomMeals)返回空数组,但函数前后的console.log能显示正确值。实现从randomMeals到dropCalendar的拖拽功能时,只有触发页面重渲染后才能成功拖拽。
核心组件(HomepageCalender)代码
import React, { useState } from 'react'; import './HomepageCalender.css'; import { Meal } from '../../types'; import RecipeCard from '../RecipeCard/RecipeCard'; import { useDrop } from 'react-dnd'; interface HomepageCalenderProps { randomMeals: Meal[]; toggleLock: (idMeal: string) => void; } interface DroppedMeal { id: string; } const HomepageCalender: React.FC<HomepageCalenderProps> = ({ randomMeals, toggleLock }) => { const [dropCalendar, setDropCalendar] = useState<Meal[]>([]); const [{ isOver }, drop] = useDrop(() => ({ accept: 'recipe-card', drop: (droppedMeal: DroppedMeal) => { console.log('meal', droppedMeal); addRecipeCardToBoard(droppedMeal.id); // console.log('hook', randomMeals) }, collect: (monitor) => ({ isOver: !!monitor.isOver(), }), })); console.log('before funtion', randomMeals) const addRecipeCardToBoard = (id: string) => { console.log('in function', randomMeals) const filteredRandomMeals = randomMeals.filter((meal) => meal.idMeal === id); const uniqueDroppedMeals = filteredRandomMeals.filter( (meal, index, self) => index === self.findIndex((m) => m.idMeal === meal.idMeal) ); setDropCalendar((prevDropCalendar) => [...prevDropCalendar, ...uniqueDroppedMeals]); }; console.log('after function', randomMeals) return ( <div className='homepage-calendar-container'> <h4 className='date'>Date</h4> <div className='droppable-area' ref={drop}> {dropCalendar.map((meal) => ( <RecipeCard key={meal.idMeal} idMeal={meal.idMeal} strMeal={meal.strMeal} strMealThumb={meal.strMealThumb} toggleLock={toggleLock} locked={true} /> ))} </div> </div> ); }; export default HomepageCalender;
父组件(Homepage)代码
import React, { useEffect, useState } from 'react'; import Header from '../Header/Header'; import WelcomeBox from '../WelcomeBox/WelcomeBox'; import Footer from '../Footer/Footer'; import RandomMealForm from '../RandomMealForm/RandomMealForm'; import HomepageCalender from '../HomepageCalender/HomepageCalender'; import { fetchSingleRandomRecipe } from '../../apiCalls'; import { RandomMealProps } from '../../types'; const Homepage: React.FC = () => { const [numberOfMeals, setNumberOfMeals] = useState<number>(5); const [randomMeals, setRandomMeals] = useState<RandomMealProps[]>([]); const toggleLock = (idMeal: string) => { const updatedRandomMeals = randomMeals.map((meal) => { if (meal.idMeal === idMeal) { return { ...meal, locked: !meal.locked }; // Create a new object } return meal; }); setRandomMeals(updatedRandomMeals); }; useEffect(() => { const fetchData = async () => { try { const fetchedMeals = []; for (let i = 0; i < numberOfMeals; i++) { const data = await fetchSingleRandomRecipe(); if (data?.meals) { const newMeals = data.meals.map((meal: RandomMealProps) => ({ ...meal, locked: false, })); fetchedMeals.push(...newMeals); } } const uniqueMeals = fetchedMeals.filter( (meal, index, self) => index === self.findIndex((m) => m.idMeal === meal.idMeal) ); setRandomMeals(uniqueMeals); } catch (error) { console.error(error); // Handle errors here if needed } }; fetchData(); }, [numberOfMeals]); useEffect(() => { console.log('homepage', randomMeals); }, [randomMeals]); return ( <div className='homepage'> <Header /> {randomMeals.length > 0 && ( <WelcomeBox randomMeals={randomMeals} toggleLock={toggleLock} /> )} <RandomMealForm numberOfMeals={numberOfMeals} setNumberOfMeals={setNumberOfMeals} setRandomMeals={setRandomMeals} randomMeals={randomMeals} toggleLock={toggleLock} /> <HomepageCalender randomMeals={randomMeals} toggleLock={toggleLock} /> <Footer /> </div> ); }; export default Homepage;
问题原因
- 闭包捕获旧值:
addRecipeCardToBoard函数在组件初始渲染时创建,捕获了当时的randomMeals(空数组)。后续randomMeals更新时,组件重渲染会创建新的addRecipeCardToBoard,但react-dnd的useDrop回调里引用的还是旧版本的函数,触发拖拽时调用旧函数,拿到的就是空数组。 - useDrop未更新依赖:
useDrop的工厂函数没有设置依赖数组,导致它不会随着randomMeals或addRecipeCardToBoard的更新而重新创建拖拽逻辑,始终使用初始渲染时的闭包值。
解决方案
方案一:更新useDrop依赖+useCallback包裹函数
修改核心组件代码,确保函数和拖拽逻辑随randomMeals更新:
import React, { useState, useCallback } from 'react'; import './HomepageCalender.css'; import { Meal } from '../../types'; import RecipeCard from '../RecipeCard/RecipeCard'; import { useDrop } from 'react-dnd'; interface HomepageCalenderProps { randomMeals: Meal[]; toggleLock: (idMeal: string) => void; } interface DroppedMeal { id: string; } const HomepageCalender: React.FC<HomepageCalenderProps> = ({ randomMeals, toggleLock }) => { const [dropCalendar, setDropCalendar] = useState<Meal[]>([]); // 用useCallback包裹,依赖更新时函数重新创建 const addRecipeCardToBoard = useCallback((id: string) => { console.log('in function', randomMeals); const filteredRandomMeals = randomMeals.filter((meal) => meal.idMeal === id); const uniqueDroppedMeals = filteredRandomMeals.filter( (meal, index, self) => index === self.findIndex((m) => m.idMeal === meal.idMeal) ); setDropCalendar((prev) => [...prev, ...uniqueDroppedMeals]); }, [randomMeals]); const [{ isOver }, drop] = useDrop(() => ({ accept: 'recipe-card', drop: (droppedMeal: DroppedMeal) => { console.log('meal', droppedMeal); addRecipeCardToBoard(droppedMeal.id); }, collect: (monitor) => ({ isOver: !!monitor.isOver(), }), }), [addRecipeCardToBoard]); // 依赖函数,确保拖拽逻辑更新 console.log('before function', randomMeals); console.log('after function', randomMeals); return ( <div className='homepage-calendar-container'> <h4 className='date'>Date</h4> <div className='droppable-area' ref={drop}> {dropCalendar.map((meal) => ( <RecipeCard key={meal.idMeal} idMeal={meal.idMeal} strMeal={meal.strMeal} strMealThumb={meal.strMealThumb} toggleLock={toggleLock} locked={true} /> ))} </div> </div> ); }; export default HomepageCalender;
方案二:直接在drop回调中处理逻辑
如果不需要复用addRecipeCardToBoard,可以直接把逻辑写在drop回调里,同时给useDrop加上randomMeals依赖:
const [{ isOver }, drop] = useDrop(() => ({ accept: 'recipe-card', drop: (droppedMeal: DroppedMeal) => { console.log('meal', droppedMeal); console.log('in drop', randomMeals); const filteredRandomMeals = randomMeals.filter((meal) => meal.idMeal === droppedMeal.id); const uniqueDroppedMeals = filteredRandomMeals.filter( (meal, index, self) => index === self.findIndex((m) => m.idMeal === meal.idMeal) ); setDropCalendar((prev) => [...prev, ...uniqueDroppedMeals]); }, collect: (monitor) => ({ isOver: !!monitor.isOver(), }), }), [randomMeals]);
方案三:用useRef保存最新数组
如果不想频繁更新useDrop依赖,可以用ref同步最新的randomMeals:
import React, { useState, useRef, useEffect } from 'react'; // ...其他代码 const HomepageCalender: React.FC<HomepageCalenderProps> = ({ randomMeals, toggleLock }) => { const [dropCalendar, setDropCalendar] = useState<Meal[]>([]); const latestRandomMeals = useRef<Meal[]>([]); // 同步最新数组到ref useEffect(() => { latestRandomMeals.current = randomMeals; }, [randomMeals]); const addRecipeCardToBoard = (id: string) => { // 从ref取最新值 const meals = latestRandomMeals.current; console.log('in function', meals); const filteredRandomMeals = meals.filter((meal) => meal.idMeal === id); const uniqueDroppedMeals = filteredRandomMeals.filter( (meal, index, self) => index === self.findIndex((m) => m.idMeal === meal.idMeal) ); setDropCalendar((prev) => [...prev, ...uniqueDroppedMeals]); }; const [{ isOver }, drop] = useDrop(() => ({ accept: 'recipe-card', drop: (droppedMeal: DroppedMeal) => { console.log('meal', droppedMeal); addRecipeCardToBoard(droppedMeal.id); }, collect: (monitor) => ({ isOver: !!monitor.isOver(), }), })); // ...其他代码 };
说明
方案一和二更符合React状态管理逻辑,可读性更高,优先推荐;方案三适合需要避免频繁更新拖拽逻辑的场景。
内容的提问来源于stack exchange,提问作者Danielle Sweeny
相关产品推荐
相关产品推荐

