连续反应 useEffect 状态变量 console.logs 不同的值

React useEffect state variable console.logs different values consecutively

我目前正在使用 Mapbox 编写地图组件。但是我在开发过程中遇到了React hooks的错误。

useEffect 中声明的状态变量打印两个不同的值。

正如我在下面的代码中所解释的那样。第二次单击 <IconButton/> 按钮后,startDrawing truefalse 都是 console.logs。

import React from "react";
import mapboxgl from "mapbox-gl";
import { Add, Delete } from "@material-ui/icons";
import IconButton from "@material-ui/core/IconButton";

export default function MapComponent() {
  const mapContainerRef = React.useRef(null);

  const [startDrawing, setStartDrawing] = React.useState(false);
  const [map, setMap] = React.useState(null);

  const initMap = () => {
    mapboxgl.accessToken = "mapbox-token";

    const mapbox = new mapboxgl.Map({
      container: mapContainerRef.current,
      style: "mapbox://styles/mapbox/streets-v11",
      center: [0, 0],
      zoom: 12,
    });

    setMap(mapbox);
  };

  React.useEffect(() => {
    if (!map) {
      initMap();
    } else {
      map.on("click", function (e) {
        
        //  After second click on set drawing mode buttons
        //   startDrawing value writes two values for each map click

        // MapComponent.js:85 true
        // MapComponent.js:85 false

        // MapComponent.js:85 true
        // MapComponent.js:85 false

        // MapComponent.js:85 true
        // MapComponent.js:85 false

        // MapComponent.js:85 true
        // MapComponent.js:85 false

        // MapComponent.js:85 true
        // MapComponent.js:85 false

        console.log(startDrawing);
        if (startDrawing) {
          // do stuff
        } else {
          // do stuff
        }
      });
    }
  }, [map, startDrawing]);

  return (
    <>
      <div>
        {/*  set drawing mode */}
        <IconButton onClick={() => setStartDrawing(!startDrawing)}>
          {startDrawing ? <Delete /> : <Add />}
        </IconButton>
      </div>

      <div ref={mapContainerRef} />
    </>
  );
}

所以我的问题是如何解决这个问题?

感谢您的回答。

问题是 useEffect 会在 mountunmount 上触发(渲染和销毁)。 Refer to this documentation 详细解释。

给运行函数在第一个render,你可以传递一个空数组作为useEffect的第二个参数,就像这样:

useEffect(()=>{
    //do stuff
},[]); // <--- Look at this parameter

最后一个参数作为flag,通常state 应该传递,这将使useEffect的功能触发仅当参数的值与之前的不同

假设您想要触发 useEffect并且每次您的state.map变化 - 你冷做以下:

const [map, setMap] = React.useState(null);
useEffect(()=>{
   //do stuff
},map); // if map is not different from previous value, function won't trigger

问题是每次 startDrawing 更改时,您都会向 map 添加一个新的事件侦听器。当您单击呈现的元素时,所有这些侦听器都将被触发,这意味着您将获得组件所看到的 startDrawing 的每个状态。

查看这个稍微更通用的代码示例,请注意,每次单击“添加”或“删除”时,都会将一个新的事件侦听器添加到目标元素:

const { useState, useRef, useEffect } = React;

function App() {
  const targetEl = useRef(null);

  const [startDrawing, setStartDrawing] = useState(false);
  const [map, setMap] = useState(null);

  const initMap = () => {
    setMap(targetEl.current);
  };

  useEffect(() => {
    if (!map) {
      initMap();
    } else {
      const log = () => console.log(startDrawing);
      map.addEventListener('click', log);
    }
  }, [map, startDrawing]);

  return (
    <div>
      <div>
        <button onClick={() => setStartDrawing(!startDrawing)}>
          {startDrawing ? <span>Delete</span> : <span>Add</span>}
        </button>
      </div>

      <div ref={targetEl}>target element</div>
    </div>
  );
}

ReactDOM.render(<App/>, document.getElementById('root'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>

您可以通过向 useEffect 添加 return 语句来解决此问题。这是在使用依赖项数组的新值更新效果之前立即触发的,也是在组件卸载时触发的。在 return 语句中,您应该删除之前的事件侦听器,以便在任何给定点上只有一个附加到元素。对于上面的示例,它看起来像这样:

useEffect(() => {
  if (!map) {
    initMap();
  } else {
    const log = () => console.log(startDrawing);
    map.addEventListener('click', log);
    return () => {
      map.removeEventListener('click', log);
    };
  };
}, [map, startDrawing]);

理想情况下,您根本不会使用标准的 JS 事件语法,因为 React 中的约定是在 return/render 函数中以声明方式附加事件,以便它们始终可以引用当前状态。但是,您使用的是外部库,我不知道它是否对 React 有任何明确的支持 - 您可能应该检查一下。