当我在不同组件的输入框中键入内容时,React Mapbox 组件会闪烁
React Mapbox component flickering when I type in the input box of a different component
我想在一个页面上显示地图和输入表单。用户应该能够在输入字段中键入内容,点击提交,然后将 Mapbox 组件移动到该位置。
我有两个问题,我怀疑它们是相关的。当我在输入框中键入内容时,地图组件会在每个 onChange 事件后闪烁。其次,当我提交表单时,地理定位被成功获取,道具在地图组件上更新并且地图标记出现,但是地图没有执行我刚进入 React Dev 工具并手动看到的整洁动画更新道具。
我知道我是新手,我敢肯定这有上千个问题。如果有人知道这个问题的答案,我会很高兴,但如果有人能指出我正确的方向,我也会很高兴。我花了一上午时间查看 React 的“入门”和“hooks”部分,并在构建简单应用程序的地方学习了一些 React 类,但显然有一些基础知识没有深入。谢谢!
这是 NextJS 页面:
import ReactMapboxGl, { Marker } from 'react-mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
import { useState } from 'react';
export default function TestPage() {
const Map = ReactMapboxGl({
accessToken: process.env.NEXT_PUBLIC_MAPBOX,
});
const [searchLocation, setSearchLocation] = useState('');
const [geolocate, setGeolocate] = useState({
coordinates: [2.61878695312962, 47.8249046208979],
isPin: false,
});
async function fetchGeolocate(location) {
const url = `https://api.mapbox.com/geocoding/v5/mapbox.places/${location}.json?access_token=${process.env.NEXT_PUBLIC_MAPBOX}`;
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.text();
})
.then((myText) => {
const json = JSON.parse(myText);
const coordinates = json.features[0].center;
setGeolocate({ coordinates, isPin: true });
});
}
function handleInputChange(event) {
setSearchLocation(event.target.value);
}
function handleForm(e) {
e.preventDefault();
fetchGeolocate(searchLocation);
}
return (
<>
<Map
style="mapbox://styles/mapbox/streets-v11"
containerStyle={{
height: '50vh',
width: '100vw',
}}
center={geolocate.coordinates}
zoom={[3]}
>
{geolocate.isPin && (
<Marker coordinates={geolocate.coordinates}>
<span className="text-3xl" role="img" aria-label="push-pin">
</span>
</Marker>
)}
</Map>
<form onSubmit={handleForm}>
<div className="flex flex-col mb-4">
<label
htmlFor="search"
className="flex flex-col uppercase font-bold text-lg text-gray-700"
>
Location
<input
type="text"
name="search"
placeholder="Location"
value={searchLocation}
className="border py-2 px-3 text-gray-700 block"
onChange={handleInputChange}
/>
</label>
</div>
<button type="submit">Find</button>
</form>
</>
);
}
您认为问题是相关的是正确的。一个问题是您在每次渲染时都重新初始化 Map
组件:
const Map = ReactMapboxGl({
accessToken: process.env.NEXT_PUBLIC_MAPBOX,
});
它也会阻止动画的发生(我认为),因为它会创建一个新的 Map 实例,这会导致 React unmount/remount 组件。
编辑:来自以下 OP 的回答:在组件外部初始化
看来您可以在任何呈现逻辑之外初始化组件。
替代方法:使用状态
在功能组件中,我们希望将初始化逻辑放在 useEffect 中(或者如果它发生在 之前 首次渲染是至关重要的,useLayoutEffect
)。
const [map, setMap] = useState()
useEffect(() => {
const mapInit = ReactMapboxGl({
accessToken: process.env.NEXT_PUBLIC_MAPBOX,
})
setMap(mapInit)
}, [])
最后的[]
是关键。它可以防止我们的回调在第一次之后被调用。
老实说,您的组件可能足够复杂以保证使用 class 组件,您可以在其中使用 componentDidMount
进行初始化,而所有这些辅助函数都不必是重新计算每个渲染。不过最终还是由你决定。
最后一点:您已将 fetchGeolocate
声明为 async
,这意味着您可以使用 await
而不是对所有结果调用 .then
想。或者拿走 async
并继续使用 Promise.then
,由您决定。 Mozilla's docs 可能有助于澄清差异。
正如@elhil 上面所说,问题基本上是我在每次渲染时重新初始化地图。所要做的只是将地图组件初始化移出 main 函数。我还采纳了@elhil 的建议并修复了我的异步等待函数。下面的代码。
import ReactMapboxGl, { Marker } from 'react-mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
import { useState } from 'react';
const Map = ReactMapboxGl({
accessToken: process.env.NEXT_PUBLIC_MAPBOX,
});
export default function TestPage() {
const [searchLocation, setSearchLocation] = useState('');
const [geolocate, setGeolocate] = useState({
coordinates: [2.61878695312962, 47.8249046208979],
isPin: false,
});
async function fetchGeolocate(location) {
const url = `https://api.mapbox.com/geocoding/v5/mapbox.places/${location}.json?access_token=${process.env.NEXT_PUBLIC_MAPBOX}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const json = await response.json();
const coordinates = json.features[0].center;
setGeolocate({ coordinates, isPin: true });
}
function handleInputChange(event) {
setSearchLocation(event.target.value);
}
function handleForm(e) {
e.preventDefault();
fetchGeolocate(searchLocation);
}
return (
<>
<Map
style="mapbox://styles/mapbox/streets-v11"
containerStyle={{
height: '50vh',
width: '100vw',
}}
center={geolocate.coordinates}
zoom={[3]}
>
{geolocate.isPin && (
<Marker coordinates={geolocate.coordinates}>
<span className="text-3xl" role="img" aria-label="push-pin">
</span>
</Marker>
)}
</Map>
<form onSubmit={handleForm}>
<div className="flex flex-col mb-4">
<label
htmlFor="search"
className="flex flex-col uppercase font-bold text-lg text-gray-700"
>
Location
<input
type="text"
name="search"
placeholder="Location"
value={searchLocation}
className="border py-2 px-3 text-gray-700 block"
onChange={handleInputChange}
/>
</label>
</div>
<button type="submit">Find</button>
</form>
</>
);
}
我想在一个页面上显示地图和输入表单。用户应该能够在输入字段中键入内容,点击提交,然后将 Mapbox 组件移动到该位置。
我有两个问题,我怀疑它们是相关的。当我在输入框中键入内容时,地图组件会在每个 onChange 事件后闪烁。其次,当我提交表单时,地理定位被成功获取,道具在地图组件上更新并且地图标记出现,但是地图没有执行我刚进入 React Dev 工具并手动看到的整洁动画更新道具。
我知道我是新手,我敢肯定这有上千个问题。如果有人知道这个问题的答案,我会很高兴,但如果有人能指出我正确的方向,我也会很高兴。我花了一上午时间查看 React 的“入门”和“hooks”部分,并在构建简单应用程序的地方学习了一些 React 类,但显然有一些基础知识没有深入。谢谢!
这是 NextJS 页面:
import ReactMapboxGl, { Marker } from 'react-mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
import { useState } from 'react';
export default function TestPage() {
const Map = ReactMapboxGl({
accessToken: process.env.NEXT_PUBLIC_MAPBOX,
});
const [searchLocation, setSearchLocation] = useState('');
const [geolocate, setGeolocate] = useState({
coordinates: [2.61878695312962, 47.8249046208979],
isPin: false,
});
async function fetchGeolocate(location) {
const url = `https://api.mapbox.com/geocoding/v5/mapbox.places/${location}.json?access_token=${process.env.NEXT_PUBLIC_MAPBOX}`;
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.text();
})
.then((myText) => {
const json = JSON.parse(myText);
const coordinates = json.features[0].center;
setGeolocate({ coordinates, isPin: true });
});
}
function handleInputChange(event) {
setSearchLocation(event.target.value);
}
function handleForm(e) {
e.preventDefault();
fetchGeolocate(searchLocation);
}
return (
<>
<Map
style="mapbox://styles/mapbox/streets-v11"
containerStyle={{
height: '50vh',
width: '100vw',
}}
center={geolocate.coordinates}
zoom={[3]}
>
{geolocate.isPin && (
<Marker coordinates={geolocate.coordinates}>
<span className="text-3xl" role="img" aria-label="push-pin">
</span>
</Marker>
)}
</Map>
<form onSubmit={handleForm}>
<div className="flex flex-col mb-4">
<label
htmlFor="search"
className="flex flex-col uppercase font-bold text-lg text-gray-700"
>
Location
<input
type="text"
name="search"
placeholder="Location"
value={searchLocation}
className="border py-2 px-3 text-gray-700 block"
onChange={handleInputChange}
/>
</label>
</div>
<button type="submit">Find</button>
</form>
</>
);
}
您认为问题是相关的是正确的。一个问题是您在每次渲染时都重新初始化 Map
组件:
const Map = ReactMapboxGl({
accessToken: process.env.NEXT_PUBLIC_MAPBOX,
});
它也会阻止动画的发生(我认为),因为它会创建一个新的 Map 实例,这会导致 React unmount/remount 组件。
编辑:来自以下 OP 的回答:在组件外部初始化
看来您可以在任何呈现逻辑之外初始化组件。
替代方法:使用状态
在功能组件中,我们希望将初始化逻辑放在 useEffect 中(或者如果它发生在 之前 首次渲染是至关重要的,useLayoutEffect
)。
const [map, setMap] = useState()
useEffect(() => {
const mapInit = ReactMapboxGl({
accessToken: process.env.NEXT_PUBLIC_MAPBOX,
})
setMap(mapInit)
}, [])
最后的[]
是关键。它可以防止我们的回调在第一次之后被调用。
老实说,您的组件可能足够复杂以保证使用 class 组件,您可以在其中使用 componentDidMount
进行初始化,而所有这些辅助函数都不必是重新计算每个渲染。不过最终还是由你决定。
最后一点:您已将 fetchGeolocate
声明为 async
,这意味着您可以使用 await
而不是对所有结果调用 .then
想。或者拿走 async
并继续使用 Promise.then
,由您决定。 Mozilla's docs 可能有助于澄清差异。
正如@elhil 上面所说,问题基本上是我在每次渲染时重新初始化地图。所要做的只是将地图组件初始化移出 main 函数。我还采纳了@elhil 的建议并修复了我的异步等待函数。下面的代码。
import ReactMapboxGl, { Marker } from 'react-mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
import { useState } from 'react';
const Map = ReactMapboxGl({
accessToken: process.env.NEXT_PUBLIC_MAPBOX,
});
export default function TestPage() {
const [searchLocation, setSearchLocation] = useState('');
const [geolocate, setGeolocate] = useState({
coordinates: [2.61878695312962, 47.8249046208979],
isPin: false,
});
async function fetchGeolocate(location) {
const url = `https://api.mapbox.com/geocoding/v5/mapbox.places/${location}.json?access_token=${process.env.NEXT_PUBLIC_MAPBOX}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const json = await response.json();
const coordinates = json.features[0].center;
setGeolocate({ coordinates, isPin: true });
}
function handleInputChange(event) {
setSearchLocation(event.target.value);
}
function handleForm(e) {
e.preventDefault();
fetchGeolocate(searchLocation);
}
return (
<>
<Map
style="mapbox://styles/mapbox/streets-v11"
containerStyle={{
height: '50vh',
width: '100vw',
}}
center={geolocate.coordinates}
zoom={[3]}
>
{geolocate.isPin && (
<Marker coordinates={geolocate.coordinates}>
<span className="text-3xl" role="img" aria-label="push-pin">
</span>
</Marker>
)}
</Map>
<form onSubmit={handleForm}>
<div className="flex flex-col mb-4">
<label
htmlFor="search"
className="flex flex-col uppercase font-bold text-lg text-gray-700"
>
Location
<input
type="text"
name="search"
placeholder="Location"
value={searchLocation}
className="border py-2 px-3 text-gray-700 block"
onChange={handleInputChange}
/>
</label>
</div>
<button type="submit">Find</button>
</form>
</>
);
}