首页 > 解决方案 > 通过改变反应状态来更改反应传单层网址会引发错误-> TypeError:无法读取未定义的属性“调用”

问题描述

我正在尝试使用以下代码构建具有可选和动态图层 URL 的地图。

当我尝试按状态更改图层 URL 时,它会在附加的屏幕截图中显示错误。

我已将所有图块存储在图块数组变量中,并且试图通过更改图块的索引来更改 URL。我第一次运行该项目时它工作正常,但是当我单击按钮并尝试更改图块的索引以呈现另一个地图图块时,它会在屏幕截图中显示错误。我还应该提到,开发人员工具 chrome 的控制台选项卡中没有显示错误。有没有办法解决这个问题?任何想法将不胜感激:)

错误截图:


const LeafletContainer = () => {
  const [baseViewCoords, setBaseViewCoords] = useState([37.715, 44.8611]);
  const [map, setMap] = useState();

  const merged_20181223 = L.tileLayer(
    "dymmyurl",
    { tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
  );

  const merged_20180924 = L.tileLayer(
    "dymmyurl",
    { tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
  );

  const merged_20190323 = L.tileLayer(
    "dymmyurl",
    { tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
  );

  const merged_20190626 = L.tileLayer(
    "dymmyurl",
    { tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
  );

  const merged_20190919 = L.tileLayer(
    "dymmyurl",
    { tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
  );

  const merged_20191218 = L.tileLayer(
    "dymmyurl",
    { tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
  );

  const merged_20200625 = L.tileLayer(
    "dymmyurl",
    { tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
  );

  const merged_20200918 = L.tileLayer(
    "dymmyurl",
    { tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
  );

  const tiles = [
    merged_20190323,
    merged_20191218,
    merged_20181223,
    merged_20180924,
    merged_20190626,
    merged_20190919,
    merged_20200625,
    merged_20200918,
  ];

  const [leftTileIndex, setLeftTileIndex] = useState(0);
  const [rightTileIndex, setTRightTileIndex] = useState(7);

  const changeTileHandler = () => {
    setLeftTileIndex((prev) => {
      if (leftTileIndex === tiles.length - 1) {
        return 0;
      }
      return prev + 1;
    });
  };

  useEffect(() => {
    if (map) {
      L.control
        .splitMap(
          L.tileLayer(tiles[leftTileIndex]._url, {
            tms: true,
            opacity: 1,
            attribution: "",
            minZoom: 1,
            maxZoom: 16,
          }).addTo(map),
          L.tileLayer(tiles[rightTileIndex]._url, {
            tms: true,
            opacity: 1,
            attribution: "",
            minZoom: 1,
            maxZoom: 16,
          })
            .addTo(map)
            .addTo(map)
        )
        .addTo(map);
    }
  }, [map, leftTileIndex, rightTileIndex]);

  useEffect(() => {
    if (map) {
      map.setView(baseViewCoords);
    }
  }, [map, baseViewCoords]);

  return (
    <div style={{ position: "relative" }}>
      <SearchArea
        {...{
          changeTileHandler,
        }}
      />
      <Options />
      <Coordinate {...{ baseViewCoords }} />
      <div id="map">
        <MapContainer
          style={{ height: "100%" }}
          center={baseViewCoords}
          zoom={9}
          scrollWheelZoom={true}
          whenCreated={(map) => {
            setMap(map);
          }}
        >
          <TileLayer
            tms={true}
            minZoom={1}
            maxZoom={16}
            opacity={1}
            attribution=""
            url={tiles[leftTileIndex]._url}
          />

          <TileLayer
            minZoom={1}
            maxZoom={16}
            opacity={1}
            tms={true}
            attribution=""
            url={tiles[rightTileIndex]._url}
          />
        </MapContainer>
      </div>
    </div>
  );
};

export default LeafletContainer;

标签: reactjsreact-hooksleafletreact-leaflet

解决方案


我不确定为什么这个错误会像leafet 的Events课程一样深,但我确实看到了一个主要问题。在您的 useEffect 中,每次用户触发状态更改时,都会将setLeftTileIndexsetRightTileIndex实例L.Control.splitmap添加到地图中。虽然编写 react-leaflet<TileLayer />组件是为了处理 url 更改并自动更新底层L.TileLayer,但您的版本L.control.splitmap不是 - 它只是添加一个新的拆分图。同时,旧的指的是不再存在的L.TileLayer。您应该使用leaflet-splitmap'ssetLeftLayerssetRightLayers方法:

// initialize this outside the useEffect
const [splitMapAdded, setSplitMapAdded] = useState(false)

const splitMap = L.control.splitMap(
  L.tileLayer("left_side_initial_url", { options }),
  L.tileLayer("right_side_initial_url", { options })
)

useEffect(() => {
  if (map && !splitMapAdded) {
    splitmap.addTo(map);
    setSplitMapAdded()true
  }
}, [map]);

// Set the splitmap left side when leftTileIndex changes
useEffect(() => {
  if (map && splitMapAdded) {
    splitMap.setLeftLayers(
      [L.tileLayer(tiles[leftTileIndex]._url], 
      { options }
    )
  }
}, [map, leftTileIndex])

// Set the splitmap left side when leftTileIndex changes
useEffect(() => {
  if (map && splitMapAdded) {
    splitMap.setRightLayers(
      [L.tileLayer(tiles[rightTileIndex]._url], 
      { options }
    )
  }
}, [map, rightTileIndex])

所以现在 splitmap 控件被添加到组件挂载上,并带有一些初始值。当这些状态变量发生变化时,您无需重新创建控件,而是使用其内部方法来更改 url。希望这能让您开始挖掘其中的一些错误。

另一种方式:

我还可以补充一点,这可以通过创建 react-leaflet v3 自定义组件来巧妙地管理。您可以使用 create 函数创建组件:

const createSplitMap = (props, context) => {

  const instance = L.control.splitmap(
    L.tileLayer(props.leftTileLayerUrl, leftTileLayerOptions),
    L.tileLayer(props.rightTileLayerUrl, rightTileLayerOptions)
  )

  return { instance, context }

}

那么你的更新函数可能如下所示:

const updateSplitMap = (instance, props, prevProps) => {

  if (prevProps.leftTileLayerUrl !== props.leftTileLayerUrl){
    instance.setLeftLayers(L.tileLayer(props.leftTileLayerUrl))
  }

  if (prevProps.rightTileLayerUrl !== props.rightTileLayerUrl){
    instance.setLeftLayers(L.tileLayer(props.rightTileLayerUrl))
  }

}

总而言之,您可以使用createLayerComponent工厂函数:

const SplitMap = createLayerComponent(createSplitMap, updateSplitMap);
export SplitMap

现在在您的地图中,您可以使用此组件:

<MapContainer {...mapContainerProps}>
  <SplitMap 
    rightTileLayerUrl={tiles[rightTileIndex]._url}
    leftTileLayerUrl={tiles[leftTileIndex]._url}
  />
  <TileLayer {...tileLayer1Props} />
  <TileLayer {...tileLayer2Props} />
</MapContainer>

我没有对此进行测试,但这是您用于为 splitmap 创建 react-leaflet 自定义组件的一般模式。


推荐阅读