VisibilityDetector 只是组件曝光触发器,不应该成为首页加载策略本身。
真正应该是:
配置驱动的预加载系统而不是:
看到组件了才临时加载正确模型应该是这样
后台配置里应该带加载策略:
{
"id": "goods_recommend_001",
"type": "goodsList",
"loadPolicy": {
"mode": "preload",
"priority": 80,
"preloadDistance": 1200,
"trigger": "afterFirstScreen",
"cachePolicy": "staleWhileRevalidate"
}
}也就是说,每个组件不是简单的 lazy,而是有一套完整策略:
class ComponentLoadPolicy {
final ComponentLoadMode mode;
final int priority;
final double preloadDistance;
final ComponentLoadTrigger trigger;
final ComponentCachePolicy cachePolicy;
final bool allowConcurrent;
final Duration? timeout;
}加载模式
enum ComponentLoadMode {
immediate, // 进入页面立刻加载
firstScreen, // 首屏优先加载
preload, // 根据距离提前加载
lazy, // 真正曝光后加载
manual, // 手动触发
cacheOnly, // 只读缓存
}触发方式
enum ComponentLoadTrigger {
onPageEnter, // 页面进入
afterLayoutReady, // 布局配置完成
afterFirstScreen, // 首屏加载完成后
nearViewport, // 接近可视区域
onVisible, // 真正曝光
onUserIdle, // 用户空闲
onPullRefresh, // 下拉刷新
}首页真实加载流程应该是
1. 进入首页
2. 读取缓存 layout
3. 请求最新 layout
4. 根据 layout 生成 ComponentLoadPlan
5. 先执行 immediate / firstScreen
6. 首屏完成后,执行 preload 队列
7. 根据组件距离 viewport 提前加载
8. 用户空闲时加载低优先级组件
9. 真正曝光时只做兜底检查重点是:
曝光不是加载策略
曝光只是最后一道保险推荐结构
HomeLoadPlanner
↓
ComponentLoadScheduler
↓
ComponentPreloadController
↓
ComponentLoader分别负责:
HomeLoadPlanner:根据后台配置生成加载计划
ComponentLoadScheduler:按优先级、并发数、触发条件执行
ComponentPreloadController:根据滚动位置和预加载距离判断
ComponentLoader:真正请求组件数据示例
class HomeLoadPlanner {
List<ComponentLoadTask> buildPlan(List<HomeComponent> components) {
final tasks = components.map((component) {
return ComponentLoadTask(
componentId: component.id,
priority: component.loadPolicy.priority,
trigger: component.loadPolicy.trigger,
preloadDistance: component.loadPolicy.preloadDistance,
);
}).toList();
tasks.sort((a, b) => b.priority.compareTo(a.priority));
return tasks;
}
}执行时不是组件自己决定加载,而是 Scheduler 决定:
class ComponentLoadScheduler {
Future<void> runInitialTasks(List<ComponentLoadTask> tasks) async {
final firstScreenTasks = tasks.where((task) {
return task.trigger == ComponentLoadTrigger.onPageEnter ||
task.trigger == ComponentLoadTrigger.afterLayoutReady;
});
await Future.wait(
firstScreenTasks.map((task) => loader.load(task.componentId)),
);
}
void onScroll(double offset, double viewportHeight) {
final preloadTasks = tasks.where((task) {
return task.shouldPreload(offset, viewportHeight);
});
for (final task in preloadTasks) {
loader.loadIfNeeded(task.componentId);
}
}
}页面层应该变成这样
NotificationListener<ScrollNotification>(
onNotification: (notification) {
viewModel.onHomeScroll(
pixels: notification.metrics.pixels,
viewportDimension: notification.metrics.viewportDimension,
);
return false;
},
child: ListView.builder(
itemCount: state.components.length,
itemBuilder: (context, index) {
final component = state.components[index];
return HomeComponentRenderer(
component: component,
);
},
),
)组件不负责判断加载。
组件只负责展示:
loading
success
failed
empty更高级一点
每个组件可以配置:
{
"id": "flash_sale_001",
"type": "flashSale",
"loadPolicy": {
"mode": "immediate",
"priority": 100,
"maxRetry": 2,
"timeoutMs": 3000,
"cachePolicy": "networkFirst",
"preloadDistance": 0
}
}推荐商品:
{
"id": "recommend_001",
"type": "goodsList",
"loadPolicy": {
"mode": "preload",
"priority": 30,
"trigger": "afterFirstScreen",
"preloadDistance": 1600,
"cachePolicy": "staleWhileRevalidate"
}
}底部模块:
{
"id": "brand_area_001",
"type": "brandArea",
"loadPolicy": {
"mode": "lazy",
"priority": 10,
"trigger": "nearViewport",
"preloadDistance": 600,
"cachePolicy": "cacheFirst"
}
}所以最终应该改成
VisibilityDetector 不是核心方案
ScrollNotification + LayoutPosition + LoadPolicy 才是核心方案最终首页架构是:
Home Layout Config
↓
HomeLoadPlanner
↓
ComponentLoadTask Queue
↓
ComponentLoadScheduler
↓
Scroll/Idle/PageEnter Trigger
↓
ComponentLoader
↓
ComponentState Update
↓
HomeComponentRenderer这才符合你说的“热插拔 + 任务流 + 配置驱动”。