使用 mapbox 和 react context 时防止过度重新渲染
Preventing excessive rerendering when using mapbox and react context
我有一个使用 mapbox 地图的应用程序,我试图允许从另一个单独的卡片组件调整视口。我采用的方法是创建视口上下文并将视口和将其设置为地图和卡片组件的函数传递。
这行得通,但我 运行 遇到了重新渲染的问题。每当用户在地图上滚动时,视口都会不断变化,因此视口提供程序中的所有内容都会不断重新渲染。代码的简化版本如下
应用程序
<ViewportProvider>
*Container that holds both the map and the sidebar that renders the card components*
</ViewportProvider>
视口提供者
import React from "react";
const INITIAL_STATE = {
longitude: 15,
latitude: 25,
zoom: 1
};
export const ViewportContext = React.createContext();
export const ViewportProvider = (props) => {
const [viewport, setViewport] = React.useState(INITIAL_STATE);
return (
<ViewportContext.Provider
value={{
viewport,
setViewport,
}}
{...props}
/>
);
}
地图
import React, { useContext } from "react";
import ReactMapGL from "react-map-gl";
import { ViewportContext } from "./ViewportProvider";
const { viewport, setViewport } = useContext(ViewportContext);
<Map onViewportChange={nextViewport => setViewport(nextViewport)} {...viewport}/>
卡片
const {setViewport} = useContext(ViewportContext)
<Card onClick={setViewport(...newValue)}/>
当用户在地图上移动时如何停止过度重新渲染而不失去用户从卡片组件调整视口的能力?
通过将 viewport
和 setViewport
放在相同的上下文中,您将在每次视口更改时强制侧边栏重新呈现,因为它使用 useContext(ViewportContext)
— 即使它仅使用上下文的 setViewport
部分。有几个选项:
- 通过简单地将
useContext(ViewportContext)
移动到子组件中来减少重新渲染对性能的影响,以便在更新视口时仅渲染子组件,而不是整个侧边栏。
- 如果您只需要单向更新(侧边栏按钮可以设置地图的视口,但其他组件不需要知道用户何时在地图上移动),那么您可以从地图中删除 setViewport 调用。
- 将
viewport
和 setViewport
放在不同的上下文中。这将意味着两次 createContext()
调用,但您仍然会保留一个呈现两个提供程序的 ViewportProvider 组件,例如const [viewport, setViewport] = useState(); return <Context1.Provider value={viewport}><Context2.Provider value={setViewport}> ...
。然后,您的侧边栏可以 useContext
仅针对它需要的上下文,并且不会在另一个更改时重新呈现。
- 使用
useContextSelector
之类的东西将 const {setViewport} = useContext(ViewportContext)
替换为 const setViewport = useContextSelector(ViewportContext, (ctx) => ctx.setViewport)
。
- 如果您正在考虑更广泛的架构更改,React Redux or Recoil 等库可以提供可避免此问题的替代设计。
(顺便说一句,在你编写的代码中,我建议你将传递给 ViewportContext.Provider
的 value
包装在 useMemo(() => ({viewport, setViewport}), [viewport, setViewport])
中,这样它就不会触发如果视口未更改,则在重新渲染 ViewportProvider 时进行更新。)
我有一个使用 mapbox 地图的应用程序,我试图允许从另一个单独的卡片组件调整视口。我采用的方法是创建视口上下文并将视口和将其设置为地图和卡片组件的函数传递。
这行得通,但我 运行 遇到了重新渲染的问题。每当用户在地图上滚动时,视口都会不断变化,因此视口提供程序中的所有内容都会不断重新渲染。代码的简化版本如下
应用程序
<ViewportProvider>
*Container that holds both the map and the sidebar that renders the card components*
</ViewportProvider>
视口提供者
import React from "react";
const INITIAL_STATE = {
longitude: 15,
latitude: 25,
zoom: 1
};
export const ViewportContext = React.createContext();
export const ViewportProvider = (props) => {
const [viewport, setViewport] = React.useState(INITIAL_STATE);
return (
<ViewportContext.Provider
value={{
viewport,
setViewport,
}}
{...props}
/>
);
}
地图
import React, { useContext } from "react";
import ReactMapGL from "react-map-gl";
import { ViewportContext } from "./ViewportProvider";
const { viewport, setViewport } = useContext(ViewportContext);
<Map onViewportChange={nextViewport => setViewport(nextViewport)} {...viewport}/>
卡片
const {setViewport} = useContext(ViewportContext)
<Card onClick={setViewport(...newValue)}/>
当用户在地图上移动时如何停止过度重新渲染而不失去用户从卡片组件调整视口的能力?
通过将 viewport
和 setViewport
放在相同的上下文中,您将在每次视口更改时强制侧边栏重新呈现,因为它使用 useContext(ViewportContext)
— 即使它仅使用上下文的 setViewport
部分。有几个选项:
- 通过简单地将
useContext(ViewportContext)
移动到子组件中来减少重新渲染对性能的影响,以便在更新视口时仅渲染子组件,而不是整个侧边栏。 - 如果您只需要单向更新(侧边栏按钮可以设置地图的视口,但其他组件不需要知道用户何时在地图上移动),那么您可以从地图中删除 setViewport 调用。
- 将
viewport
和setViewport
放在不同的上下文中。这将意味着两次createContext()
调用,但您仍然会保留一个呈现两个提供程序的 ViewportProvider 组件,例如const [viewport, setViewport] = useState(); return <Context1.Provider value={viewport}><Context2.Provider value={setViewport}> ...
。然后,您的侧边栏可以useContext
仅针对它需要的上下文,并且不会在另一个更改时重新呈现。 - 使用
useContextSelector
之类的东西将const {setViewport} = useContext(ViewportContext)
替换为const setViewport = useContextSelector(ViewportContext, (ctx) => ctx.setViewport)
。 - 如果您正在考虑更广泛的架构更改,React Redux or Recoil 等库可以提供可避免此问题的替代设计。
(顺便说一句,在你编写的代码中,我建议你将传递给 ViewportContext.Provider
的 value
包装在 useMemo(() => ({viewport, setViewport}), [viewport, setViewport])
中,这样它就不会触发如果视口未更改,则在重新渲染 ViewportProvider 时进行更新。)