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

React中如何优化isLoading检查?寻求简化骨架屏代码方案

解决React加载状态下自动替换嵌套属性为骨架屏的方案

核心思路

无需在深层组件中重复编写isLoading三元判断,通过高阶组件(HOC)、Context+自定义钩子或React Suspense三种方案,拦截属性渲染逻辑,在加载状态下自动将嵌套属性(如user.address.street)替换为骨架屏组件,同时保留原有JSX结构的简洁性。


方案一:高阶组件(HOC) + 递归属性拦截

适合全局统一处理场景,封装HOC自动递归遍历组件props,加载状态下替换空/未定义的嵌套值为骨架屏。

实现代码

import React from 'react';
import Skeleton from './Skeleton'; // 项目中的骨架屏组件

// 递归处理属性,加载时替换空值为骨架屏
const replaceWithSkeleton = (value, isLoading) => {
  if (!isLoading) return value;
  // 处理null/undefined/空字符串
  if (value == null || value === '') return <Skeleton />;
  // 处理嵌套对象:递归遍历子属性
  if (typeof value === 'object' && !React.isValidElement(value)) {
    return Object.fromEntries(
      Object.entries(value).map(([key, val]) => [key, replaceWithSkeleton(val, isLoading)])
    );
  }
  // 其他情况返回原内容
  return value;
};

// 高阶组件:包裹目标组件,自动注入加载状态处理逻辑
const withSkeletonFallback = (Component) => {
  return ({ isLoading, ...props }) => {
    const processedProps = replaceWithSkeleton(props, isLoading);
    return <Component {...processedProps} />;
  };
};

// 使用示例
const UserProfile = ({ user }) => (
  <div>
    <h3>{user.name}</h3>
    <p>{user.address.street}, {user.address.city}</p>
    <p>{user.email}</p>
  </div>
);

// 用HOC包裹组件
const UserProfileWithSkeleton = withSkeletonFallback(UserProfile);

// 业务组件调用
const App = () => {
  const [isLoading, setIsLoading] = React.useState(true);
  const [user, setUser] = React.useState(null);

  React.useEffect(() => {
    setTimeout(() => {
      setUser({
        name: 'John Doe',
        address: { street: '123 Main St', city: 'New York' },
        email: 'john@example.com'
      });
      setIsLoading(false);
    }, 2000);
  }, []);

  return <UserProfileWithSkeleton isLoading={isLoading} user={user} />;
};

方案二:Context + 自定义渲染钩子

适合局部灵活控制场景,通过Context传递加载状态,用钩子在组件内部选择性替换属性为骨架屏。

实现代码

import React, { createContext, useContext } from 'react';
import Skeleton from './Skeleton';

// 创建加载状态上下文
const LoadingContext = createContext(false);

// 自定义钩子:根据加载状态返回骨架屏或原内容
const useSkeleton = (value) => {
  const isLoading = useContext(LoadingContext);
  if (!isLoading) return value;
  if (value == null || value === '') return <Skeleton />;
  return value;
};

// 上下文提供者组件
const LoadingProvider = ({ isLoading, children }) => {
  return (
    <LoadingContext.Provider value={isLoading}>
      {children}
    </LoadingContext.Provider>
  );
};

// 深层组件使用示例
const Address = ({ street, city }) => (
  <p>
    {useSkeleton(street)}, {useSkeleton(city)}
  </p>
);

const UserProfile = ({ user }) => (
  <div>
    <h3>{useSkeleton(user.name)}</h3>
    <Address street={user.address?.street} city={user.address?.city} />
    <p>{useSkeleton(user.email)}</p>
  </div>
);

// 业务组件调用
const App = () => {
  const [isLoading, setIsLoading] = React.useState(true);
  const [user, setUser] = React.useState(null);

  React.useEffect(() => {
    setTimeout(() => {
      setUser({
        name: 'John Doe',
        address: { street: '123 Main St', city: 'New York' },
        email: 'john@example.com'
      });
      setIsLoading(false);
    }, 2000);
  }, []);

  return (
    <LoadingProvider isLoading={isLoading}>
      <UserProfile user={user} />
    </LoadingProvider>
  );
};

方案三:React Suspense + 自定义数据源

React 18+官方推荐方案,配合自定义异步数据源,让组件在数据加载时自动fallback到骨架屏,完全消除手动加载状态判断。

实现代码

import React, { Suspense } from 'react';
import Skeleton from './Skeleton';

// 模拟异步数据请求
const fetchUser = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({
        name: 'John Doe',
        address: { street: '123 Main St', city: 'New York' },
        email: 'john@example.com'
      });
    }, 2000);
  });
};

// 包装成Suspense可识别的数据源
const createResource = (promise) => {
  let status = 'pending';
  let result;
  promise.then(
    (data) => {
      status = 'success';
      result = data;
    },
    (error) => {
      status = 'error';
      result = error;
    }
  );
  return {
    read() {
      if (status === 'pending') throw promise;
      if (status === 'error') throw result;
      return result;
    }
  };
};

const userResource = createResource(fetchUser());

// 组件直接读取数据,无需手动处理加载状态
const UserProfile = () => {
  const user = userResource.read();
  return (
    <div>
      <h3>{user.name}</h3>
      <p>{user.address.street}, {user.address.city}</p>
      <p>{user.email}</p>
    </div>
  );
};

// 用Suspense包裹,指定加载时的骨架屏 fallback
const App = () => {
  return (
    <Suspense fallback={
      <div>
        <Skeleton />
        <Skeleton />
        <Skeleton />
      </div>
    }>
      <UserProfile />
    </Suspense>
  );
};

方案选型建议

  • 若需全局统一处理固定结构组件,选HOC方案;
  • 若需局部精准控制差异化渲染,选Context+钩子方案;
  • 若使用React 18+且追求官方最佳实践,选Suspense方案(配合React Query、Relay等库体验更佳)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 19:58:40