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
相关产品推荐
相关产品推荐

