如何将Google Maps对象正确设置为MobX可观察对象
MobX 默认会对赋值给 @observable 修饰字段的对象执行深度响应式代理转换,而 Google Maps 暴露的 google.maps 命名空间、Map 实例都是基于原生类与原型链实现的宿主对象,自带不可枚举的内部状态、绑定了上下文的原型方法。被MobX递归代理后,对象的原型链指向会被破坏,最终导致内置方法无法正常调用,该问题和深拷贝逻辑无关。
方案1:使用引用型可观察字段存储(推荐)
这类第三方SDK的原生实例本身不需要MobX追踪其内部属性变化,你只需要全局持有一个可访问的原始引用即可,不需要做深度代理。给存储字段加 observable.ref 标记,告诉MobX直接存储原始引用、不做任何递归代理转换即可。
MobX 6 标准写法示例:
// mobx store.js import { makeObservable, observable, action } from 'mobx' class MapStore { // 存储google.maps命名空间 mapApi = null // 存储初始化后的地图实例 mapInstance = null constructor() { makeObservable(this, { mapApi: observable.ref, mapInstance: observable.ref, setMapApi: action, setMapInstance: action }) } @action setMapApi(api) { this.mapApi = api } @action setMapInstance(instance) { this.mapInstance = instance } }
如果使用装饰器简写语法,直接在字段上加修饰符即可,无需修改赋值action逻辑:
import { observable, action } from 'mobx' class MapStore { @observable.ref mapApi = null @observable.ref mapInstance = null @action setMapObject(obj) { this.mapApi = obj } }
React侧初始化逻辑调整,建议同时存储API命名空间和初始化完成的地图实例,方便后续调用:
// react 组件逻辑 const map = new window.google.maps.Map( document.getElementById('gmap'), { center, zoom } ) // 存储maps API对象 this.props.mapStore.setMapApi(window.google.maps) // 存储地图实例,后续添加标记、修改视角、绑定事件都需要用到 this.props.mapStore.setMapInstance(map)
该方案下存储的是Google Maps对象的原始引用,没有任何代理包装,原型链和内部状态完全保持原生状态,所有内置方法都可以正常调用,同时当引用被赋值/替换时,依然可以触发MobX的响应式更新,方便你监听地图SDK、实例是否初始化完成。
方案2:全局变量挂载(无响应需求时使用)
如果你不需要监听地图对象的初始化状态、不需要触发响应式更新,完全不需要将对象存入MobX store,初始化完成后直接挂载到window对象即可:
window.googleMapsApi = window.google.maps window.googleMapInstance = map
该方案实现最简单,但存在全局命名空间污染问题,且无法触发响应式更新,仅适合逻辑简单的小型项目。
注意:不要尝试对Google Maps这类持有DOM引用、内部闭包状态的原生宿主对象做深拷贝、深度代理操作,常规的对象遍历、Proxy拦截逻辑都会破坏其内部结构,导致方法报错、状态异常。所有第三方SDK暴露的原生类实例,在MobX中存储时都建议使用
observable.ref做引用存储。
内容的提问来源于stack exchange,提问作者ian.jaden

