React ref.current 在 componentDidUpdate 中仍然是 null

React ref.current is still null in componentDidUpdate

我正在尝试使用 react-map-gl 放大一组坐标。为此,库鼓励我们使用 React 的 Refs。在 render 中实例化组件时,我传递了一个应该包含底层 MapBox 地图对象的引用。我设法使这种行为起作用。唯一的问题:它非常不一致。

我正在 componentDidUpdate 中调用一个名为 setCamera 的方法。但它只适用于初始负载。如果我重新加载页面,我会收到来自 Gatsby 的错误。如果我关闭错误,它将再次工作。直到下一次重新加载。

错误是this.mapRef.currentnull。在尝试访问它之前,我试图设置一个条件来验证该值不是 null ,但这会导致动画永远无法工作。无论我重新加载多少次,它都不会执行。而如果没有条件,它至少可以在崩溃前工作一半的时间。所以这本身就是一个谜,如果有人知道为什么会发生这种行为,我洗耳恭听。

不过,我并没有气馁,而是尝试将对 setCamera 的调用放在 setTimeout 中,是的,它成功了!即使设置像 1 这样的非常低的超时,代码也能在 95% 的时间内正常工作。但我对此并不满意,因为我明白放置那种计时器不是我应该做的,而且让事情变得更糟的是,它并不能始终如一地解决问题。

我对这个问题的理解是,出于某种原因,componentDidUpdate 中仍未设置 MapRef。它有时会被设置。我不知道 React 是否支持线程,或者某种异步巫术是否在幕后欺骗我,但我想知道的是 我什么时候能保证我的 ref 被正确设置? 我应该在哪里或如何编写这段代码?

提前感谢任何可以帮助我的人。

这是我的示例代码:

import React, {Component} from 'react';
import MapGL, {NavigationControl, Source, Layer} from 'react-map-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
import gpxParser from 'gpxparser';
import bbox from '@turf/bbox';

const MAPBOX_TOKEN = 'exampleToken'

class HikeMapbox extends Component {
    constructor(props) {
        super(props)

        this.state = {
            gpxData: '',
        };
    }

    // Read et get the GPX file, return it as a bunch of text
    componentDidMount() {
        const gpxPath = `https:${this.props.gpxPath}`;

        // Simple GET request using fetch
        fetch(gpxPath)
            .then(response => response.text())
            .then(text => this.setState({ gpxData: text }));
    }

    componentDidUpdate() {
        // Set the camera on didUpdate
        setTimeout(() => {
            const geoJsonData = this.getGeoJson();
            this.setCamera(geoJsonData);
        }, 10);
    }

    // Get Max and Min Lat and Lon to Zoom on the GPX trace
    setCamera(geoJsonData) {
        if (geoJsonData) {
            const [minLng, minLat, maxLng, maxLat] = bbox(geoJsonData);
            this.mapRef.current.fitBounds(
                [
                    [minLng, minLat],
                    [maxLng, maxLat]
                ],
                {padding: 40}
            );
        }
    }

    // Take the text and parse it as geoJSON
    getGeoJson() {
        const { gpxData } = this.state;
        let gpx = new gpxParser();
        
        try {
            gpx.parse(gpxData);
        } catch (err) {
            console.log(err);
        }

        const geoJson = gpx.toGeoJSON();   

        return geoJson
    }

    // Return the GPX trace in Mapbox
    showGpxFile() {
        const GeoJsonData = this.getGeoJson();

        // Choose the style of the GPX trace
        const layerStyle = {
            id:'contours',
            type:'line',
            source:'contours',
            paint: {
                'line-color': 'blue',
                'line-width': 3
            }
        };

        return (
            <>
                {
                // Return the React Mapbox GL code to show the GPX file on the map
                GeoJsonData && (
                    <Source type="geojson" data={GeoJsonData}>
                        <Layer {...layerStyle} />
                    </Source>
                )
                }
            </>
        )
    }


  render() {
      this.mapRef = React.createRef();
      return (
        <>
        <MapGL
            ref={this.mapRef}
            initialViewState={{
              latitude: this.props.latitude,
              longitude: this.props.longitude,
              zoom: 8,
            }}
            style={{width: "100%", height: "100%"}}
            mapStyle="mapbox://styles/mapbox/outdoors-v11"
            mapboxAccessToken={MAPBOX_TOKEN}
        >
        <NavigationControl />
        {this.showGpxFile()}
        </MapGL>
        </>
    )
  }
  
}

export default HikeMapbox;

顺便说一下,我 运行 在我的计算机上使用 gatsby develop 这段代码。我不知道这是否相关,但我认为可能相关。

我找到了解决办法!

我的问题是 setCamera 取决于两个条件,并且这两个条件可能以任何顺序发生。

  1. 获取成功,我们有数据要显示。
  2. 地图已加载,我们有它的参考。

我没有在构造函数或 render 中初始化 mapRef,而是创建了一个函数…

onMapRefChange = node => {
    this.setState({mapRef: node});
    // I'm calling my method now
    this.setCamera()
};

... 我在 ref 参数中传递

<MapGL
    ref={this.onMapRefChange}
    ...

最终 onMapRefChange 将收到一个实际的地图对象,然后 setCamera 中的代码将能够访问它。