页面学习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 级别的优化实践。

相关推荐
TDengine (老段)1 小时前
网络延时对 TDengine TSDB 写入性能的影响:实验解析与实践建议
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
ZKNOW甄知科技2 小时前
AI-ITSM的时代正在到来:深度解读Gartner最新报告
大数据·运维·人工智能·低代码·网络安全·微服务·重构
xinyuan_1234562 小时前
数智化招采平台实战指南:AI如何让采购管理实现效率与价值落地
大数据·人工智能
Tezign_space2 小时前
技术实战:Crocs如何构建AI驱动的智能内容矩阵,实现内容播放量提升470%?
大数据·人工智能·矩阵·aigc·内容运营·多智能体系统·智能内容矩阵
八月瓜科技2 小时前
八月瓜科技参与“数据要素驱动产业升级”活动,分享【数据赋能科技创新全链条】
java·大数据·人工智能·科技·机器人·程序员创富
梦里不知身是客113 小时前
flink的CDC 的种类
大数据·flink
TOWE technology3 小时前
PDU、工业连接器与数据中心机柜电力系统
大数据·人工智能·数据中心·idc·pdu·智能pdu·定制电源管理
五度易链-区域产业数字化管理平台3 小时前
行业研究+大数据+AI:“五度易链”如何构建高质量产业数据库?
大数据·人工智能