React结合Mapbox开发首次点击弹窗props更新不显示问题
问题根因
你遇到的双击才能显示数据的问题和组件感知props变更无关,是两个执行逻辑错误导致的:
- 时序错误:
getData是异步拉取数据的方法,你调用this.props.getData(id)之后没有等待请求返回、父组件state更新,就立刻同步执行弹窗创建逻辑读取this.props.testdata,此时异步请求还没结束,读到的自然是空的旧值。 - 渲染管控范围问题:
mapboxgl.Popup是Mapbox原生操作DOM生成的元素,完全脱离React虚拟DOM的渲染周期,就算后续props更新了,已经生成的弹窗内容不会自动重渲染,只有你第二次点击重新执行setHTML创建弹窗时,才能读到已经更新完成的props值。
另外你贴的代码里还有一个字段不匹配的隐患:App.js里拉取完数据setState存的字段名是data,但Map组件里读取的是this.props.testdata,需要确认父组件传给子组件的props字段名是否正确,避免字段名写错导致取值失败。
正确实现方案
优先选第一种方案,逻辑最简单、时序最可控:
- 改造
getData方法,让它返回请求Promise,把拉取到的详情数据作为Promise的返回值,点击事件里等待请求完成后,直接用返回的数据渲染弹窗,不需要依赖props传值绕一圈。
改造App.js的getData方法
给异步请求加return,把Promise暴露给调用方,请求完成后把数据return出去,同时记得catch分支也要关闭driver连接,避免连接泄漏:
getData = (id) => { if (id == null) { console.log("Missing id") return Promise.reject("invalid id"); } const {mapCenter, startDate, endDate} = this.state; const neo4j = require('neo4j-driver') const driver = neo4j.driver('bolt://xxx', neo4j.auth.basic("xx", "xx-xx")) const session = driver.session() return session .run('Here goes a neo4j cypher statment',{id: id}) .then((results)=> { const data= []; results.records.forEach((record) => data.push([record.get("r"), record.get("n"), record.get("b")])) this.setState({ data }); session.close() driver.close() return data; }).catch(e => { console.log(e) session.close(); driver.close(); return Promise.reject(e); }); };
改造Map.js的点击事件逻辑
把点击回调改成async函数,等待数据拉取完成后再创建弹窗,加载过程中可以给鼠标加等待样式,避免用户重复点击:
this.map.on('click', 'unclustered-point', async (e) => { const coordinates = e.features[0].geometry.coordinates.slice(); const id = e.features[0].properties.id; const infos = e.features[0].properties.infos; while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) { coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360; } if (id == null) { console.log("Missing id, cant get informations") return; } // 加载状态提示 this.map.getCanvas().style.cursor = 'wait'; let testdata = ''; try { testdata = await this.props.getData(id); } catch (err) { testdata = '详情加载失败'; } finally { this.map.getCanvas().style.cursor = ''; } new mapboxgl.Popup() .setLngLat(coordinates) .setHTML( ` Id: ${id} <br> Infos: ${infos} <br> <br> Testdata: ${JSON.stringify(testdata)} ` ) .addTo(this.map); }); // 其余鼠标移入移出聚类点的逻辑保持不变即可 this.map.on('mouseenter', 'clusters', () => { this.map.getCanvas().style.cursor = 'pointer'; }); this.map.on('mouseleave', 'clusters', () => { this.map.getCanvas().style.cursor = ''; });
如果你因为业务需求必须从props里取testdata渲染,就需要额外维护一份选中点的状态:点击标记时先记录当前点的id、坐标,不立刻渲染弹窗,等componentDidUpdate监听到props.testdata变化、且当前有选中点时,再创建/更新弹窗内容,弹窗关闭时清空选中点状态。这种方案逻辑更繁琐,没有特殊需求不推荐使用。
内容的提问来源于stack exchange,提问作者Ritzu
相关产品推荐
相关产品推荐

