连续反应 useEffect 状态变量 console.logs 不同的值
React useEffect state variable console.logs different values consecutively
我目前正在使用 Mapbox 编写地图组件。但是我在开发过程中遇到了React hooks的错误。
在 useEffect
中声明的状态变量打印两个不同的值。
正如我在下面的代码中所解释的那样。第二次单击 <IconButton/>
按钮后,startDrawing
true
和 false
都是 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
会在 mount
和 unmount
上触发(渲染和销毁)。 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 有任何明确的支持 - 您可能应该检查一下。
我目前正在使用 Mapbox 编写地图组件。但是我在开发过程中遇到了React hooks的错误。
在 useEffect
中声明的状态变量打印两个不同的值。
正如我在下面的代码中所解释的那样。第二次单击 <IconButton/>
按钮后,startDrawing
true
和 false
都是 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
会在 mount
和 unmount
上触发(渲染和销毁)。 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 有任何明确的支持 - 您可能应该检查一下。