页面学习1

1. 模拟 API 文件 (api.js)

用于模拟一个耗时 3 秒的后端请求。

javascript 复制代码
// src/api.js

/**
 * 模拟后端接口请求
 * 延迟 3000ms 返回数据
 */
export function fetchData() {
  return new Promise((resolve, reject) => {
    console.log('>>> [Network] 请求已发出 (开始计时)...');
    
    setTimeout(() => {
      console.log('<<< [Network] 请求已完成 (数据返回)!');
      // 模拟成功返回
      resolve({
        code: 200,
        data: {
          id: 101,
          title: "Vue 3 异步加载优化演示",
          description: "这是一个非常大的数据对象,加载它需要耗费很多时间...",
          list: ["数据项 A", "数据项 B", "数据项 C", "数据项 D"]
        }
      });
      // 如果想测试失败情况,可以在这里 reject(new Error('网络错误'))
    }, 3000);
  });
}

2. 子组件 (Component2.vue)

负责:接收 Promise -> 显示 Loading -> 等待 Promise 解决 -> 渲染数据。

html 复制代码
<template>
  <div class="comp2-wrapper">
    <div v-if="loading" class="loading-state">
      <div class="spinner"></div>
      <span>数据拼命加载中...</span>
    </div>

    <div v-else-if="error" class="error-state">
      ❌ 加载失败: {{ error }}
    </div>

    <div v-else class="content-state">
      <h3>📄 {{ backendData.title }}</h3>
      <p class="desc">{{ backendData.description }}</p>
      <ul>
        <li v-for="(item, index) in backendData.list" :key="index">
          {{ item }}
        </li>
      </ul>
      <div class="success-badge">✅ 组件2 JS 逻辑开始执行</div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';

// 接收父组件传递过来的 Promise,而不是具体数据
const props = defineProps({
  requestPromise: {
    type: Promise,
    required: true
  }
});

const loading = ref(true);
const error = ref(null);
const backendData = ref(null);

onMounted(async () => {
  try {
    loading.value = true;
    console.log('Component2: 已挂载,开始 await 传入的 Promise...');

    // 核心逻辑:这里会等待父组件发起的请求结果
    // 1. 如果请求还在进行,这里会卡住,loading 保持为 true
    // 2. 如果请求已经完成,这里瞬间通过
    const res = await props.requestPromise;
    
    // 假设后端返回结构是 { code: 200, data: ... }
    backendData.value = res.data;
    
    console.log('Component2: 拿到数据,渲染页面');
  } catch (err) {
    console.error('Component2: 请求出错', err);
    error.value = err.message || '未知错误';
  } finally {
    // 无论成功失败,关闭 loading
    loading.value = false;
  }
});
</script>

<style scoped>
.comp2-wrapper {
  margin-top: 20px;
  border: 1px solid #dcdfe6;
  border-radius: 8px;
  padding: 20px;
  background: #fdfdfd;
  min-height: 150px; /* 保持最小高度防止抖动 */
}

/* Loading 动画样式 */
.loading-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100%;
  color: #909399;
}
.spinner {
  width: 30px;
  height: 30px;
  border: 3px solid #ebeef5;
  border-top: 3px solid #409eff;
  border-radius: 50%;
  animation: spin 1s linear infinite;
  margin-bottom: 10px;
}
@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

/* 内容样式 */
.content-state h3 {
  margin-top: 0;
  color: #303133;
}
.desc {
  color: #606266;
}
.success-badge {
  margin-top: 15px;
  display: inline-block;
  background: #e1f3d8;
  color: #67c23a;
  padding: 5px 10px;
  border-radius: 4px;
  font-size: 12px;
  font-weight: bold;
}
.error-state {
  color: #f56c6c;
}
</style>

3. 父组件 (Component1.vue)

负责:页面一加载就抢跑(预加载请求),持有 Promise,点击按钮才展示子组件。

html 复制代码
<template>
  <div class="comp1-container">
    <h2>组件 1 (主界面)</h2>
    <p>当前页面已渲染,后台网络请求已在 API 层启动。</p>
    
    <div class="control-panel">
      <button @click="handleOpen" :disabled="showComp2" class="primary-btn">
        {{ showComp2 ? '组件2 已打开' : '点击打开 组件2' }}
      </button>
      
      <button @click="handleReset" class="reset-btn">
        重置测试
      </button>
    </div>

    <div class="tip">
      <small>提示:快速点击按钮可以看到 Loading;等待3秒后再点击则秒开。</small>
    </div>

    <Component2 
      v-if="showComp2" 
      :request-promise="dataPromise" 
    />
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import { fetchData } from '../api'; // 引入模拟API
import Component2 from './Component2.vue';

const showComp2 = ref(false);

// 用于存储请求的 Promise 对象(注意:不是存数据,是存 Promise)
const dataPromise = ref(null);

// 封装发起请求的逻辑
const startPrefetch = () => {
  // 核心优化:
  // 在这里调用接口,但不 await。
  // 这样请求就开始在网络层传输了,利用了用户的思考时间。
  dataPromise.value = fetchData(); 
};

onMounted(() => {
  // 组件1挂载完毕,立即发起请求
  startPrefetch();
});

const handleOpen = () => {
  showComp2.value = true;
};

// 仅仅为了演示方便,重置状态
const handleReset = () => {
  showComp2.value = false;
  dataPromise.value = null;
  console.clear();
  // 稍微延迟一下重新发起请求,模拟用户刷新页面
  setTimeout(() => {
    startPrefetch();
    alert('已重置,请求已重新并在后台发起。请再次尝试点击。');
  }, 100);
};
</script>

<style scoped>
.comp1-container {
  max-width: 600px;
  margin: 40px auto;
  padding: 30px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
  border-radius: 12px;
  font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
.control-panel {
  margin: 20px 0;
  display: flex;
  gap: 10px;
}
button {
  padding: 10px 20px;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
  transition: opacity 0.2s;
}
button:hover {
  opacity: 0.9;
}
.primary-btn {
  background-color: #409eff;
  color: white;
}
.primary-btn:disabled {
  background-color: #a0cfff;
  cursor: not-allowed;
}
.reset-btn {
  background-color: #f4f4f5;
  color: #909399;
}
.tip {
  color: #999;
  margin-bottom: 20px;
}
</style>

使用方法

  1. 新建上述三个文件。
  2. 在你的 App.vue 中引入并使用 Component1
html 复制代码
<template>
  <Component1 />
</template>

<script setup>
import Component1 from './components/Component1.vue';
</script>

预期运行结果

  1. 打开页面 :你会看到控制台打印 >>> 请求已发出
  2. 立刻点击按钮Component2 会显示 "数据拼命加载中..." 的动画。等待几秒后,动画消失,数据出现。
  3. 重置后等待 3 秒再点击 :你会看到控制台先打印 <<< 请求已完成。此时点击按钮,Component2瞬间显示数据(Loading 动画几乎不可见),因为 Promise 已经处于 Resolved 状态。

确实,之前的方案更多是解决了"数据等待"和"重复渲染"的问题。

针对 GIS 组件初次加载慢 (通常是因为 OpenLayers/Leaflet 库很大,或者初始化地图需要大量的 DOM 计算和 WebGL 上下文创建),我们需要采用 "错峰加载" (Deferred Loading) 的策略。

核心思路是:不要让 GIS 组件拖慢首屏,但要在用户点击之前悄悄把它渲染好。

这里有三招进阶优化,按推荐程度排序:

第一招:异步组件 + 浏览器空闲预加载 (最佳实践)

GIS 相关的库(如 OpenLayers)体积通常很大。如果直接 import GIS from './GIS.vue',这部分代码会打在主包里,导致页面整体变慢。

我们需要:

  1. 代码分包:把 GIS 组件拆分成独立 js 文件。
  2. 空闲加载 :首屏渲染完 TopologyGraph 后,利用浏览器空闲时间悄悄加载 GIS 的代码并渲染 DOM。

修改代码:

html 复制代码
<template>
  <div class="portal-framework-a">
    <div v-show="!isActiveViews">
      <TopologyGraph ... />
    </div>

    <div v-if="shouldLoadGis" v-show="isActiveViews">
       <AsyncGIS 
         :request-promise="dataPromise" 
         class="portal-map-gis" 
         @hook:mounted="onGisMounted"
       />
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, defineAsyncComponent } from 'vue';
import TopologyGraph from './TopologyGraph.vue';

// 1. 异步引入 GIS 组件 (代码分包)
// 这样 GIS 的庞大代码库不会阻塞首屏加载
const AsyncGIS = defineAsyncComponent(() => 
  import('./GIS.vue')
);

const isActiveViews = ref(false);
const shouldLoadGis = ref(false); // 控制 GIS 是否开始"预热"

onMounted(() => {
  // 2. 错峰加载策略
  // 页面主内容渲染完成后,延迟 1-2 秒,或者使用 requestIdleCallback
  // 此时用户正在看 TopologyGraph,浏览器空闲,我们在后台悄悄把 GIS 加载并渲染出来
  
  if ('requestIdleCallback' in window) {
    requestIdleCallback(() => {
      shouldLoadGis.value = true;
      console.log('浏览器空闲,开始静默加载 GIS 组件...');
    });
  } else {
    // 降级方案
    setTimeout(() => {
      shouldLoadGis.value = true;
    }, 2000); 
  }
});

// 监听 GIS 挂载完成
const onGisMounted = () => {
  console.log('GIS 组件已在后台渲染完毕,随时待命!');
};
</script>

第二招:解决"隐形渲染"的地图白屏问题 (关键)

既然我们要在后台(display: none 状态下)渲染 GIS,绝大多数地图引擎(OpenLayers/ECharts)都会遇到一个坑:当容器宽高为 0 时,地图无法正确计算视口,导致切过去时是一片白或者乱掉。

你需要在 GIS 组件内部处理这个问题。

GIS.vue 中:

javascript 复制代码
<script setup>
import { onActivated, watch } from 'vue';

// 假设 props 接收 isActiveViews 或者父组件通过 v-show 控制
// 这里的关键是:当你第一次把 display: none 变成 display: block 时
// 必须通知地图引擎 "我的容器大小变了,请重绘"

// 方案 A: 如果你用了 KeepAlive,用 onActivated
onActivated(() => {
  if (mapInstance) {
    mapInstance.updateSize(); // OpenLayers 的 API
    // mapInstance.resize();  // ECharts 的 API
  }
});

// 方案 B: 监听外部传入的显示状态 (推荐)
// 假设父组件传了个 prop: isVisible
watch(() => props.isVisible, (newVal) => {
  if (newVal && mapInstance) {
    // 稍微给一点点延时,确保 DOM display 属性已经生效
    setTimeout(() => {
      mapInstance.updateSize(); 
    }, 100);
  }
});
</script>

第三招:Web Worker 数据预处理 (针对海量数据)

如果你的 GIS 组件慢不是因为代码大,而是因为要处理几万个点位的 GeoJSON 数据,那么 JS 主线程会卡死。

可以将数据解析放到 Web Worker 中。

  1. 主线程dataPromise 发起请求。
  2. Worker :接收 API 返回的原始 JSON,解析成 OpenLayers 需要的 Feature 格式(序列化后)或者做聚类计算。
  3. 主线程:拿到处理好的精简数据直接渲染。

(如果你的数据量在 5000 条以内,这一步通常不需要,前两招足够。)

总结方案

要达到极致的初次加载速度,请按以下步骤修改:

  1. 拆分代码 :用 defineAsyncComponent 引入 GIS 组件。
  2. 错峰渲染 :使用 v-if + setTimeout/requestIdleCallback,让 GIS 组件在首屏加载完 2 秒后,在用户还没点击按钮时,就自动在后台加载并渲染 DOM。
  3. 配合 v-show :渲染出来后,利用外层的 v-show 保持隐藏。
  4. 修正地图 :在点击按钮切换(v-show 变 true)的瞬间,调用 map.updateSize() 修复地图尺寸。

这样用户点击按钮时,实际上是在看一个已经画好的图,速度就是 0ms。


针对 GIS 组件(特别是基于 OpenLayers 或 WebGL 的组件)的首次加载(First Meaningful Paint)优化,业界的最优实践是结合 "代码分包"、"空闲预热""离屏渲染" 三大策略。

既然是资深前端,我们直接上硬核方案。这里有一套组合拳,目标是将用户点击切换时的等待时间压缩至 0ms

核心策略:错峰预热 + 幽灵渲染 (Ghost Rendering)

传统的懒加载(Lazy Load)是"点击了才去加载",这会导致点击后有明显的 JS 下载和执行延迟。
最优实践是: 在用户浏览 TopologyGraph 时(浏览器的主线程空闲时),提前下载 GIS 代码,并提前初始化 地图实例,但不可见


实施步骤与代码实现

1. 拆分代码 (Code Splitting)

GIS 引擎(OpenLayers)体积很大,必须从主包中剥离。

2. 浏览器空闲检测 (Idle Detection)

不要在 onMounted 立刻加载 GIS,那样会抢占 TopologyGraph 的渲染资源。使用 requestIdleCallback 在浏览器休息时加载。

3. 幽灵模式 (The "Ghost" Mode) ------ 关键点

这是解决地图初始化卡顿的核心。

  • 通常我们用 v-show (display: none) 隐藏组件,但 OpenLayers 在 display: none 容器中无法正确初始化视口(Size),导致切出来时需要 resize 甚至白屏。
  • 黑科技解法 :使用 visibility: hiddenopacity: 0 + z-index: -1 + position: absolute。让地图在视觉上消失,但在 DOM 树中拥有真实的宽/高,这样 OpenLayers 可以在后台静默完成所有图层渲染。

完整代码方案

这是一个封装好的 Vue 3 优化结构:

html 复制代码
<template>
  <div class="portal-framework-a" style="position: relative; height: 100%;">
    
    <div v-show="!isGisActive" class="view-container">
      <TopologyGraph />
    </div>

    <div 
      v-if="hasStartedLoading"
      :class="['gis-container', { 'ghost-mode': !isGisActive }]"
    >
       <AsyncGIS 
         :request-promise="dataPromise" 
         @hook:mounted="onMapMounted"
       />
    </div>

    <div v-if="isGisActive && !isMapReady" class="global-loading">
       地图资源初始化中...
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, defineAsyncComponent } from 'vue';
import TopologyGraph from './TopologyGraph.vue';

// [优化1]: 异步引入,Webpack/Vite 会自动将其拆分为独立 chunk
const AsyncGIS = defineAsyncComponent(() => import('./GIS.vue'));

// 状态管理
const isGisActive = ref(false);       // 用户是否切换到了 GIS 视图
const hasStartedLoading = ref(false); // 是否开始下载 GIS 代码
const isMapReady = ref(false);        // GIS 组件是否已经 mount 完成

// [优化2]: 智能预加载策略
onMounted(() => {
  // 定义预加载任务
  const preloadTask = () => {
    console.log('🚀 [Idle] 浏览器空闲,开始静默下载并渲染 GIS...');
    hasStartedLoading.value = true;
  };

  // 使用 requestIdleCallback 在主线程空闲时执行
  // 兼容性处理:如果不支持,延时 2-3 秒执行
  if ('requestIdleCallback' in window) {
    requestIdleCallback(preloadTask, { timeout: 5000 });
  } else {
    setTimeout(preloadTask, 3000);
  }
});

// 监听 GIS 组件内部 mount 完成
const onMapMounted = () => {
  console.log('✅ [Ready] GIS 地图已在后台渲染完毕,DOM 尺寸计算正确!');
  isMapReady.value = true;
};

// 用户点击切换按钮
const toggleView = () => {
  // 如果用户点击太快,预加载还没开始,现在立刻开始
  if (!hasStartedLoading.value) {
    hasStartedLoading.value = true;
  }
  isGisActive.value = !isGisActive.value;
};
</script>

<style scoped>
.view-container, .gis-container {
  width: 100%;
  height: 100%;
}

/* [优化3]: 幽灵模式样式 (核心黑科技)
  不使用 display: none,因为那会导致 OpenLayers 无法计算 canvas 大小。
  使用 opacity + absolute 将其藏在底层,但保持渲染活性。
*/
.ghost-mode {
  position: absolute;
  top: 0;
  left: 0;
  z-index: -9999; /* 藏在最下面 */
  opacity: 0;     /* 视觉不可见 */
  pointer-events: none; /* 禁止交互 */
  /* 保持 width/height 为 100% 从而让地图引擎能正确计算视口 */
}

/* 正常模式:恢复层级和可见性 */
.gis-container:not(.ghost-mode) {
  position: relative;
  z-index: 1;
  opacity: 1;
  transition: opacity 0.3s ease; /* 加个淡入效果更丝滑 */
}
</style>

为什么这是最优解?

  1. JS 解析不阻塞defineAsyncComponent 配合 requestIdleCallback,保证了 TopologyGraph 的首屏渲染绝对流畅,JS 下载发生在 CPU 闲得发慌的时候。
  2. 避免 Resize 闪烁 :这是最关键的。使用 .ghost-mode(透明度隐藏)而不是 v-show(display隐藏)。
    • 如果是 display: none :OpenLayers 初始化检测到容器宽为 0,停止渲染。等你切过去变成 block,它才开始计算并绘图,你会看到一瞬间的白屏或错位,且 CPU 占用率瞬间飙升。
    • 如果是 opacity: 0 :OpenLayers 以为自己显示了,它会在后台把 Canvas 画好,把瓦片加载好。当你切过去时,只是浏览器合成层(Compositor)变一下透明度,耗时几乎为 0
  3. 数据并行 :配合你之前的 Promise 预加载方案,地图逻辑加载和业务数据请求是并行的。

进阶:如果数据量极大 (Web Worker)

如果 GIS 初始化不仅仅是加载地图,还需要解析 MB 级别 的 GeoJSON 数据,那么即使是"幽灵渲染"也会让主线程卡顿(导致页面滚动掉帧)。

极客做法:

GIS.vue 中,将数据解析逻辑移入 Web Worker

javascript 复制代码
// GIS.vue 伪代码
import { onMounted } from 'vue';

onMounted(async () => {
  // 1. 主线程:初始化空地图
  const map = initMap();
  
  // 2. Worker 线程:解析繁重的 GeoJSON
  const worker = new Worker(new URL('./gis-parser.worker.js', import.meta.url));
  worker.postMessage({ rawData: props.rawData });
  
  worker.onmessage = (e) => {
    // 3. 主线程:直接接收处理好的 Buffer 或轻量级对象进行渲染
    const features = deserialize(e.data);
    vectorSource.addFeatures(features);
  };
});

对于 95% 的业务场景,"空闲预热 + 幽灵渲染" 已经足够实现秒开体验。


这个分析非常精准!"依赖接口配置才能创建地图" 是很多复杂 GIS 应用的性能杀手。

如果这 5 秒的接口请求是在 GIS 组件 onMounted 之后才发起的,那么无论怎么优化代码分包,用户体验都会有一个硬性的 5 秒白屏

既然我们已经实施了 "幽灵渲染(Ghost Rendering)" ,我们完全可以将 "配置预加载(Config Prefetching)" 融合进去,打一套组合拳。

这不仅能优化,而且能完美解决 这个问题。我们需要实施 "并行数据流 + 缓存优先" 策略。


核心思路:打破"串行等待"

现状(串行):

  1. 页面加载
  2. 闲置/点击
  3. 加载 GIS 组件 JS
  4. GIS 组件挂载 (onMounted)
  5. 发起配置请求 (耗时 5s) <-- 瓶颈在这里
  6. 拿到配置 -> new Map()

优化后(并行):

  1. 页面加载 -> 立即发起配置请求 (Promise)
  2. 闲置时间 -> 下载 GIS 组件 JS & 后台挂载组件
  3. GIS 组件拿到父级传来的 Promise -> await
    • 情况 A:用户看拓扑图看了 5 秒以上 -> 配置早已回来 -> 地图秒出
    • 情况 B:用户进来就切地图 -> 配置缓存策略 (LocalStorage) -> 地图秒出

解决方案 A:配置请求前置 (Promise 穿透)

不要让 GIS 组件自己去请求配置,而是让**父组件(Portal)**一上来就请求,然后把这个"正在进行的请求"传给 GIS 组件。

修改父组件 (Portal.vue):

javascript 复制代码
<script setup>
import { ref, onMounted } from 'vue';
import { getMapConfigApi } from '@/api/map';

// 1. 关键点:在父组件 setup 时立即发起请求
// 不需要 await,我们要的是这个 Promise 对象
// 这样页面一打开,这 5 秒的倒计时就已经开始了!
const mapConfigPromise = getMapConfigApi(); 

// ... 其他代码 (GIS 组件的引入等)
</script>

<template>
  <AsyncGIS 
    :config-promise="mapConfigPromise"
    class="gis-ghost-mode"
  />
</template>

修改 GIS 组件 (GIS.vue):

javascript 复制代码
<script setup>
const props = defineProps({
  configPromise: Promise // 接收配置的 Promise
});

onMounted(async () => {
  // 组件挂载时,直接等待这个已经跑了一会儿的 Promise
  const config = await props.configPromise;
  
  // 拿到配置,初始化地图
  initMap(config);
});
</script>

解决方案 B:缓存优先策略 (Stale-While-Revalidate) ------ 针对 5秒 延迟的终极杀招

既然配置接口要 5 秒,说明后端计算量大。但地图的底图配置(如投影坐标系、瓦片地址、初始缩放级别)通常不会频繁变更

我们可以使用 LocalStorage 进行"伪秒开"。

优化后的请求逻辑 (可以在 api.js 或 store 中封装):

javascript 复制代码
// src/utils/mapConfigLoader.js

const CONFIG_CACHE_KEY = 'GIS_MAP_CONFIG_CACHE';

export function loadMapConfigSmart() {
  return new Promise((resolve) => {
    // 1. 先检查本地有没有缓存
    const cachedString = localStorage.getItem(CONFIG_CACHE_KEY);
    
    if (cachedString) {
      console.log('⚡️ [Cache] 命中本地地图配置,立刻渲染!');
      const cachedData = JSON.parse(cachedString);
      
      // A. 立即返回缓存数据,让地图先画出来 (解决 5s 等待)
      resolve(cachedData);
      
      // B. 默默在后台发起新请求,更新缓存,为下一次做准备
      // (这是"静默更新",用户感知不到)
      fetchConfigFromServer(); 
    } else {
      // 2. 如果没有缓存,只能老老实实等 5 秒
      console.log('⏳ [Network] 无缓存,发起耗时请求...');
      fetchConfigFromServer().then(data => resolve(data));
    }
  });
}

function fetchConfigFromServer() {
  return fetch('/api/getSlowMapConfig').then(res => res.json()).then(data => {
    // 拿到最新数据,存入缓存
    localStorage.setItem(CONFIG_CACHE_KEY, JSON.stringify(data));
    console.log('🔄 [Update] 本地地图配置缓存已更新');
    return data;
  });
}

综合完整代码 (集成之前的所有优化)

结合你现在的架构,这是能达到 "不仅组件加载快,地图初始化也快" 的最终形态:

1. 父组件 (PortalFramework.vue)
html 复制代码
<template>
  <div class="portal-framework">
    <div v-show="!isGisActive">
      <TopologyGraph />
    </div>

    <div 
      v-if="startLoad" 
      :class="['gis-wrapper', { 'ghost-mode': !isGisActive }]"
    >
      <AsyncGIS 
        :config-promise="smartConfigPromise" 
        @hook:mounted="onMapMounted"
      />
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, defineAsyncComponent } from 'vue';
import { loadMapConfigSmart } from '@/utils/mapConfigLoader'; // 上面定义的缓存工具

// 1. 异步组件 (代码拆分)
const AsyncGIS = defineAsyncComponent(() => import('./GIS.vue'));

const isGisActive = ref(false);
const startLoad = ref(false);

// 2. 关键优化:setup 时立刻启动配置获取流程
// 无论是有缓存(0ms) 还是无缓存(5s),这个 Promise 对象都立刻创建好了
const smartConfigPromise = loadMapConfigSmart();

onMounted(() => {
  // 3. 闲时加载 GIS 代码 (代码预热)
  requestIdleCallback(() => {
    startLoad.value = true;
  });
});

// ... 切换逻辑不变 ...
</script>
2. GIS 组件 (GIS.vue)
javascript 复制代码
<script setup>
import { onMounted } from 'vue';
// OpenLayers imports...

const props = defineProps({
  configPromise: Promise // 接收父组件传来的 Promise (可能是缓存的,也可能是请求中的)
});

onMounted(async () => {
  try {
    console.log('GIS组件挂载,开始等待配置...');
    
    // 1. 这里的 await 会发生什么?
    // 如果有缓存:Promise 早就 resolve 了,await 瞬间完成 -> 立即 initMap
    // 如果无缓存:await 会等待父组件里发出的那个请求完成
    const config = await props.configPromise;
    
    // 2. 初始化地图
    initMap(config);
    
  } catch (e) {
    console.error('地图配置加载失败', e);
  }
});

function initMap(config) {
  // 使用 config 创建 layer, view 等
  // new Map({ ... })
  console.log('🗺️ 地图初始化完成!');
}
</script>

预期效果对比

场景 原始方案 (你的现状) 优化方案 (Ghost + Promise提升 + 缓存)
JS代码加载 点击才加载 JS (慢) 闲时预加载 JS (无感)
地图DOM初始化 切换后才创建 DOM 幽灵模式后台创建 DOM
首次打开体验 等待 5 秒 (请求配置) 几乎 0 秒 (命中缓存)
无缓存首次打开 等待 5 秒 等待 < 5 秒 (因为请求在父组件setup时已经抢跑了)
二次切换 重复销毁创建 (卡顿) opacity 切换 (瞬间)

总结:

对于那个 5 秒的接口,"Promise 提升到父组件" 可以让这 5 秒跟用户的阅读时间重叠。而 "LocalStorage 缓存" 可以直接消灭这 5 秒。结合之前的幽灵渲染,这绝对是业界 T0 级别的优化实践。

相关推荐
大大大大晴天4 小时前
每天认识一个组件:数据治理Apache Atlas
大数据
数字新视界4 小时前
2026模块化机房选型指南:行业市场发展趋势、占有率与竞争梯队分析报告解析
大数据·人工智能·物联网·数据中心·微模块机房·模块化机房·冷通道
姜穆澜7 小时前
OneID 从 0 到 1 完整生产案例(四)
大数据
OsDepK7 小时前
项目快速Git至仓库(完整版)
大数据·git·elasticsearch·搜索引擎
合米AI SOP系统8 小时前
传统产线如何快速上马落地 AI 防错?合米科技 AI SOP 7天即可上线。
大数据·人工智能·科技
思录Echo8 小时前
什么决定具身智能的最终走向?多技术路线与落地现实辨析
大数据·人工智能
xiaohaiAIgeo8 小时前
【2026年】AI监控加行为分析守护实验室安全
大数据·人工智能·科普知识
林墨聊AIGC9 小时前
动漫AI视频创作工具在哪找到的?2026年最新动漫AI视频平台与软件指南
大数据·人工智能·ai作画·aigc·音视频
故七月10 小时前
告别 AI 时代品牌 “隐身”:万域智瞰 AI‑GEO,构建品牌大模型时代营销新基建
大数据·人工智能
数字孪生视频孪生11 小时前
三维实时重构异构底座 核工危化无感定位跨境轨迹一屏统揽
大数据·运维·人工智能·重构·架构