React Hooks - 调用依赖于 "one time" useEffect 状态的函数的最佳位置?

React Hooks- Best place to call a function that depends on state from a "one time" useEffect?

我正在尝试使用 React Hooks 实现 google 地图,而不使用任何类型的第三方库。我编写了允许生成脚本的代码,该脚本使用 url 获取 google 地图,如下所示。但是我不知道调用这个脚本生成函数的正确方法是什么(createScriptLoadMap() 在我们的例子中),因为在 useEffect 中调用它只被调用一次似乎给我一个错误:

You have included the Google Maps JavaScript API multiple times on
this page. This may cause unexpected errors.

我想知道为什么会出现此错误以及如何解决。 这是一个 stackblitz link,我已经尽力为我的情况制作了最少的代码:

https://stackblitz.com/edit/react-hsl4dn?file=Hello.js

编辑:我也在做一个来自 useEffect 的获取请求,所以它必须是 "one time" useEffect,但我必须调用 createScriptLoadMap() 一次,此功能取决于 useEffect 设置的状态,这不会立即执行!

有什么方法可以让 get 请求发生,设置 mapCenter 然后执行 createScriptLoadMap()

https://stackblitz.com/edit/react-ohtdra?file=Hello.js

Hooks 在这种情况下帮不了你。即使您从 useEffect 中 return clean-up function 并从 head 中删除 element。因为此脚本在 head (如样式) 中创建了大量其他元素,仅删除一个元素无济于事。

我需要说的是,不引入 React 就做事是不对的,但如果你需要的话,你可以。例如。在脚本标签中添加一些内容,下次检查并跳过 createScriptLoadMap。像这样:

const createScriptLoadMap = () => {
  var gScript = document.querySelector("script[data-setted]");
  var isSetted = gScript && gScript.getAttribute("data-setted");

  if (!isSetted) {
    var index = document.getElementsByTagName("script")[0];
    var script = document.createElement("script");
    script.src =
      "https://maps.googleapis.com/maps/api/js?key=AIzaSyDurZQBXjtSzKeieXwtFeGe-jhZu-HEGQU&libraries=drawing&callback=initMap";
    script.async = true;
    script.defer = true;
    script.setAttribute("data-setted", true);
    index.parentNode.insertBefore(script, index);
    window.initMap = initMap;
  } else {
    // I'm not sure about this, can cause a problem
    // but without it stackblitz doesn't render anything
    initMap();
  }
};

您的 useEffect 挂钩只执行一次。您收到该消息很可能是因为热重新加载和重新安装 Map 组件。

为了避免这种情况,只需使用您设置的 window 属性 window.initMap 检查地图脚本是否已加载:

const createScriptLoadMap = () => {
  if (!window.initMap) {
    var index = window.document.getElementsByTagName("script")[0];
    var script = window.document.createElement("script");
    script.src =
      "https://maps.googleapis.com/maps/api/js?key=AIzaSyDurZQBXjtSzKeieXwtFeGe-jhZu-HEGQU&libraries=drawing&callback=initMap";
    script.async = true;
    script.defer = true;
    index.parentNode.insertBefore(script, index);
    window.initMap = true;
  }
};

编辑

至于在获取一些数据后如何使地图居中,您还应该使用钩子和 useEffect,因为您已经有一个 mapCenter 状态对象。首先使用 script.load 查看脚本何时加载。调用 initMap 是否加载脚本以获取 google.maps.Map 对象并使用状态保存它以备后用,否则如果脚本已经加载则只需分配地图对象:

const createScriptLoadMap = () => {
  if (!window.initMap) {
    var index = window.document.getElementsByTagName("script")[0];
    var script = window.document.createElement("script");
    script.src =
      "https://maps.googleapis.com/maps/api/js?key=AIzaSyDurZQBXjtSzKeieXwtFeGe-jhZu-HEGQU&libraries=drawing&callback=initMap";
    script.async = true;
    script.defer = true;
    script.onload = () => {
      initMap();
    };
    index.parentNode.insertBefore(script, index);
    window.initMap = true;
  } else {
    initMap();
  }
};

然后改变 initMap 函数来初始化或分配一个 google.maps.Map 对象到输出 DOM #map 元素(不会创建新地图,如果我们之前已经分配了元素):

const initMap = () => {
  let googleMap = new google.maps.Map(document.getElementById("map"), {
    center: { lat: 37.978534, lng: 23.743830 }, //CURRENTLY STATIC
    //center: {mapCenter} //ACTUAL
    zoom: 14
  });

  // Save the map object created for later usage
  setMap(googleMap);

  // axios.get(`${BASE_URL_LOCAL}/outlet/list?page=1&limit=50`).then(res => {
  //   let center = averageGeolocation(res.data.data.outlets);
  //   console.log(props.mapCenter);
  //   setFetchedData(res);
  //   setMapCenter(center) //THIS CENTER IS REQUIRED IN LINE 22
  // });

  // Suppose get an Axios response after 2 seconds
  setTimeout(() => {
    // Center map at NY Brooklyn using state (effect for mapCenter will be triggered)
    setMapCenter({ lat: 40.674282, lng: -73.943060 });
  }, 2000);
};

这将创建一个 google.aps.Map 对象并使用 setMap 状态保存它供以后使用。现在每当你想更新地图的中心(或任何其他地图properties/methods),只需设置一个useEffect挂钩到mapCenter状态对象然后使用 map 对象来改变地图:

const [mapCenter, setMapCenter] = useState({});
const [fetchedData, setFetchedData] = useState({});
const [map, setMap] = useState();

useEffect(() => {
  createScriptLoadMap();
}, []);

useEffect(() => {
  if(map) {
    map.setCenter(mapCenter)
  }
}, [mapCenter]);

检查我的分叉和更新 stackblitz。您会看到地图从雅典开始,在延迟 2 秒后(假设我们有一个异步 Axios 调用),它使地图以纽约布鲁克林为中心。 (我创建了我的 Google 地图 API 密钥用于演示,我会在您回复后删除)。