mars3d项目组件生命周期优化

295 阅读2分钟

尝试用 pinia与hooks更方便地管理map相关方法

首先创建一个pinia store 用来生成map实例与操作map的一些方法

// marsMapStore.ts
import * as mars3d from "mars3d";
import type Mars3d from "mars3d";

// 各个页面map.ts中的"mapMounted"生命周期方法
type MountedFunc = ((mapIns: mars3d.Map) => void) | (() => void);

/** 
 * 读取默认配置文件
 * 默认配置文件路径 /config/config.json
 */
const defaultConfigUrl = `/config/config.json?time=${new Date().getTime()}`;

/**
 * 有关地图实例与相关方法与生命周期的store
 */
export const useMarsMapStore = defineStore("MarsMap", () => {
  // * state
  const map: Ref<Mars3d.Map | null> = ref(null);
  /** mapMounted 启动队列
   * 各个页面map.ts中的"mapMounted"生命周期方法
   * 如果页面加载时map实例还未生成,会将对应的mapMounted方法添加到启动队列,在map实例生成后再调用
   */
  const mountedList: Ref<MountedFunc[]> = ref([]);

  // * getter
  /** marsmap实例 */
  const mapIns = computed(() => map.value);

  // * action
  /**
   * 读取配置文件,创建地图
   * @param id 地图容器id
   * @param options 覆盖json读取的默认配置
   * @param options json配置文件地址,默认 public/config/config.json
   */
  const setMap = async (ElementId: string, options = {}, configUrl: string = defaultConfigUrl) => {
    const { map3d: asyncOption } = await mars3d.Util.fetchJson({ url: configUrl }); // 读取地图配置
    const option = mars3d.Util.merge(options, asyncOption); // 合并配置
    map.value = new mars3d.Map(ElementId, option); // 创建map实例
    /** 生成map实例后执行启动队列 */
    mountedList.value.forEach((mapMounted) => map.value && mapMounted(map.value));
  };

  /**
   * 启用map.ts生命周期,且 map实例未创建时,将mapMounted生命周期添加到mountedList
   * 列表中的生命周期函数将在map实例生成时被调用
   * @param fn map.ts => mapMounted生命周期函数
   * 如果有其它想要在生成map实例后执行的方法,但不确定当时map实例是否创建,也可以通过暴露的addMounted方法将其添加到启动队列
   * @example
   * // xxxx.vue
   * const store = useMarsMapStore();
   * const xxx = (xx)=>{...}
   * store.mapIns.value ? xxx(store.mapIns.value) : store.addMounted(xxx)
   */
  const addMounted = (fn: MountedFunc) => mountedList.value.push(fn);

  const removeMap = () => {
    if (map.value) {
      map.value.destroy();
      map.value = null;
      mountedList.value = [];
    }
    console.log("map销毁完成", map.value);
  };
  return { setMap, removeMap, addMounted, mapIns };
});

创建一个map容器

// app.vue
<script setup lang="ts">
  // 只有action可以直接解构,state和getter解构会丢失响应性 需要用storeToRefs包裹
  const { setMap } = useMarsMapStore();
// 传入容器id,创建map
  onMounted(() => { setMap("mars3dContainer") });
</script>

<template>
  <div id="app">
    <div id="mars3dContainer" class="mars3dContainer"></div>
    <div class="viewContainer">
      <RouterView />
    </div>
  </div>
</template>

<style lang="less" scoped>
  .mars3dContainer {
    height: 100vh;
    width: 100vw;
  }
</style>

启用map.ts生命周期的方法

// useLifeCycle.ts
export const useLifeCycle: Hooks.LifeCycle.LifeCycle = ({ mapMounted, mapUnMounted }) => {
  const mars = useMarsMapStore();

  onBeforeMount(() => {
    mapMounted && (mars.mapIns ? mapMounted(mars.mapIns) : mars.addMounted(mapMounted));
  });

  onBeforeUnmount(() => {
    mapUnMounted && mapUnMounted();
  });
};

对应dts文件

namespace Hooks {
  namespace LifeCycle {
    interface LifeCycleParams {
      mapMounted?: MountedFunc;
      mapUnMounted?: Func;
    }
    type LifeCycle = (params: LifeCycleParams) => void;
  }
}

一个标准的map.ts

import * as mars3d from "mars3d";

let map: mars3d.Map;
let tiles3dLayer: mars3d.layer.TilesetLayer;

// ! 生命周期方法
const mapMounted = (mapIns: mars3d.Map) => {
  map = mapIns;
  setTilesetLayer();
};
const mapUnMounted = () => {
  tiles3dLayer.remove();
  tiles3dLayer = null;
};
// ! 生命周期方法 end

// 加载合肥市建筑物
function setTilesetLayer() {
  // 模型
  tiles3dLayer = new mars3d.layer.TilesetLayer({
    name: "合肥市建筑物",
    url: "//data.mars3d.cn/3dtiles/jzw-hefei/tileset.json",
    maximumScreenSpaceError: 1,
    maximumMemoryUsage: 1024,
    position: { alt: -50 },
    popup: [
      { field: "objectid", name: "编号" },
      { field: "name", name: "名称" },
      { field: "height", name: "楼高", unit: "米" }
    ]
  });
  map.addLayer(tiles3dLayer);
  tiles3dLayer.on(mars3d.EventType.click, (e) => {
    console.log("被点击的楼栋数据:", e);
  });
}
// 使用对象的方式导出,以保留ts类型检查
export const lifeCycle = { mapMounted, mapUnMounted };

在对应的组件中 只需要

<script setup lang="ts">
  import * as mapWork from "./map";
  useLifeCycle(mapWork.lifeCycle)
</script>

整体逻辑:

  1. map.ts中书写方法,并在mapMounted中调用
  2. xxx.vue导入map.ts的生命周期(mapWork.lifeCycle),传递给"useLifeCycle"
  3. 组合式函数useLifeCycle会在vue生命周期中调用map.ts生命周期
    1. 当useLifeCycle运行时,如果map实例未创建,就将生命周期函数加入启动队列,map实例创建时
    2. 果map实例已创建 在vue生命周期中直接调用

回到开始的app.vue,即使给生成map实例加上延迟,也不会阻止其它组件加载

  onMounted(() => {
    setTimeout(() => {
      setMap("mars3dContainer");
    }, 5000);
  });