You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React页面加载闪烁问题求助(含组件代码)

React页面加载时闪烁问题求助

我在React项目中遇到页面加载时闪烁的问题,每次打开页面都会出现该现象。项目仅进行数据获取操作,无复杂业务逻辑。涉及的组件包括PersonalList、Favourites、SharedRecipes,这些组件仅负责数据获取与渲染。我尝试过多种数据获取方法、加载动画、setTimeout,但均导致用户体验恶化。


PersonalList组件

import "./PersonalList.css";
import "../ErrorPage/ErrorPage.css";

import React, { useLayoutEffect, useState, useEffect, useContext, ReactMapboxGl } from 'react';

import RecipePersonalModel from "../Recipe/RecipePersonalModel/RecipePersonalModel";
import { AuthContext } from "../../contexts/AuthContext";
import * as userService from '../../services/userService';
import ErrorPage from '../../components/ErrorPage/ErrorPage';
import { debounce } from 'lodash';

const PersonalList = () => {
    const [recipes, setRecipes] = useState([]);
    const { user } = useContext(AuthContext);
    
    useLayoutEffect(() => {
        userService.ownRecipes(user._id)
            .then(recipeResult => {
                setRecipes(recipeResult);
            });
    }, []);

    return (
        <div>
            <h1>Your Recipes</h1>
            <section className="secper">
                {/* {
                   recipes.length > 0
                        ? 
                        <>
                        
                        {recipes.map(x => <RecipePersonalModel key ={x._id} recipe={x}/>)}
                        </>

                        : <div className="errorpaper">
                                <h1>No recipes found.</h1>
                                <ErrorPage/>
                          </div>
                        
                } */}
                
                {recipes.map(x => <RecipePersonalModel key ={x._id} recipe={x}/>)}
            </section>
        </div>
    );
}

export default PersonalList;

Favourites组件

import "./Favourites.css";

import React, { useLayoutEffect, useState ,useEffect,useContext} from 'react';

import RecipeFavourite from "../Recipe/RecipeFavourite/RecipeFavourite";
import * as userService from '../../services/userService';
import { AuthContext } from "../../contexts/AuthContext";
import ErrorPage from "../ErrorPage/ErrorPage";

function Favourites() {
    const [favourites,setFavourites] = useState([]);
    const {user} = useContext(AuthContext);
    const count = favourites.length;

    useLayoutEffect(() => {
        userService.yourFavourites(user._id)
            .then(result => {
                setFavourites(result);
            })
    },[]);

    useLayoutEffect(() => {
        window.scrollTo(0, 0)
    });

    return (
        <div>
            <h1 className = "h1m">Your Favourite Recipes</h1>
            <section className="secfav">
                {/* {
                   favourites.length > 0
                        ? <>
                            {favourites.map(x => <RecipeFavourite key ={x._id} recipe={x} />)}
                          </>
                        : <div className="errorpaper">
                             <h1>No recipes found.</h1>
                             <ErrorPage/>
                          </div> 
                } */}
               
               {favourites.map(x => <RecipeFavourite key ={x._id} recipe={x} />)}
            </section>
        </div>
    );
}

export default Favourites;

SharedRecipes组件

import { Link, NavLink, useNavigate, useParams } from 'react-router-dom';
import React, { useContext, useEffect, useLayoutEffect, useState } from 'react';

import RecipePersonalModel from "../Recipe/RecipeModel/RecipeModel";
import RecipeFavourite from "../Recipe/RecipeFavourite/RecipeFavourite";

import * as userService from "../../services/userService";
import RecipeShared from '../Recipe/RecipeShared/RecipeShared';
import { AuthContext } from '../../contexts/AuthContext';
import ErrorPage from '../ErrorPage/ErrorPage';

const SharedRecipes = () => {
    const [recipes,setRecipes] = useState([]);
    const [recipe,setRecipe] = useState([]);
    const {recipeId} = useParams();
    const {user} = useContext(AuthContext);
   
    const recipesd = async () => {
        let reciperes =   await userService.getOne(recipeId);
        console.log(reciperes);
        setRecipe(reciperes);
    };

    useEffect(() => { recipesd(recipe); },[]);
    
    const [recipessh,setRecipessh] = useState([]);
    
    useLayoutEffect(() => {
        userService.getAll()
            .then(result => {
                setRecipessh(result);
            })
    },[]);

    const history = useNavigate();

    useLayoutEffect(() => {
        window.scrollTo(0, 0)
    });

    const publicall = recipe.publicp=="yes";

    return (
        <div>
           <h1>Shared Recipes</h1>
           <section className="sec4">
                {/* {
                   recipessh.length > 0
                        ? <>
                            {recipessh.filter(x=>x._ownerId!=user._id && x.publicp.toLowerCase()=="yes").map(x => <RecipeShared key ={x._id} recipe={x} />)}
                          </>
                        : <div className="errorpaper">
                             <h1>No recipes found.</h1>
                             
                           </div> 
                }
                */}

               {recipessh.map(x=> <RecipeShared key={x._id} recipe={x} />)}
           </section>
        </div>
    );
}

export default SharedRecipes;

问题分析与解决方案

核心原因

  1. 初始空状态渲染:组件初始时数据数组为空,直接渲染空列表,数据加载完成后触发二次渲染,造成视觉闪烁。
  2. useLayoutEffect误用:数据请求属于异步操作,useLayoutEffect会同步阻塞DOM渲染,反而可能加剧闪烁;无依赖的滚动操作会在每次渲染时执行,触发不必要的重绘。
  3. 缺失加载状态处理:原代码注释掉了空状态判断,没有加载期间的占位UI,数据从无到有的变化会形成明显闪烁。

具体修复步骤

1. 替换useLayoutEffect为useEffect处理数据请求

异步数据请求用useEffect更合适,避免阻塞DOM渲染,同时添加正确的依赖项:

// PersonalList组件示例修改
useEffect(() => {
  userService.ownRecipes(user._id)
    .then(recipeResult => setRecipes(recipeResult));
}, [user._id]);

2. 添加加载状态与占位UI

在每个组件中新增isLoading状态,加载期间显示占位内容(骨架屏/加载文字),避免空状态:

// PersonalList组件示例修改
const PersonalList = () => {
  const [recipes, setRecipes] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const { user } = useContext(AuthContext);

  useEffect(() => {
    userService.ownRecipes(user._id)
      .then(recipeResult => setRecipes(recipeResult))
      .finally(() => setIsLoading(false));
  }, [user._id]);

  return (
    <div>
      <h1>Your Recipes</h1>
      <section className="secper">
        {isLoading ? (
          <div className="loading">加载中...</div>
        ) : recipes.length > 0 ? (
          recipes.map(x => <RecipePersonalModel key={x._id} recipe={x} />)
        ) : (
          <div className="errorpaper">
            <h1>No recipes found.</h1>
            <ErrorPage />
          </div>
        )}
      </section>
    </div>
  );
};

3. 修复无依赖的滚动useLayoutEffect

给滚动操作添加空依赖数组,确保只在组件挂载时执行一次:

useLayoutEffect(() => {
  window.scrollTo(0, 0);
}, []);

4. 可选:React 18+ 启用Suspense

如果项目使用React 18,可结合Suspense统一处理加载状态,避免组件内部重复逻辑:

// 路由层示例
import { Suspense } from 'react';

<Suspense fallback={<div>加载中...</div>}>
  <PersonalList />
</Suspense>

内容的提问来源于stack exchange,提问作者DimitriTfb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 10:06:46