React useCallback钩子疑问:依赖布尔值未变化时函数仍重复渲染的问题排查
解决useCallback缓存后仍重复渲染标记的问题
看起来你遇到的核心问题是虽然用了useCallback缓存渲染函数,但每次组件渲染时还是会执行这些函数并生成新的JSX,导致标记反复渲染。让我一步步帮你拆解问题并给出解决方案:
问题根源分析
依赖数组不完整(闭包陷阱)
你的renderPlacesMarkers函数里用到了places数组,但依赖数组里只加了showPlaces——这会导致两个问题:- 当
places更新时,函数还是会引用闭包中的旧places值,显示错误的标记 - 即使
showPlaces没变,Map组件因其他状态(比如places)更新而重新渲染时,调用renderPlacesMarkers()会生成基于新places的JSX,但因为函数没缓存新的places,反而会导致不必要的渲染逻辑执行
- 当
直接调用缓存函数仍会生成新JSX
即使useCallback缓存了函数本身,每次Map组件渲染时调用{renderPlacesMarkers()},都会执行函数内部的places.map(...),生成全新的JSX元素数组。GoogleMap组件会对比子元素的引用,每次都是新数组,所以会重新渲染所有标记。
具体修复步骤
第一步:补全useCallback的依赖数组(必须先做)
首先要把函数中用到的所有外部变量都加入依赖数组,避免闭包问题:
const renderPlacesMarkers = useCallback(() => { console.log('rendering places') if(showPlaces){ return places.map(place => ( <Marker icon={icon} key={place.ID} title={place.Name} position={{lat:place.Latitude,lng:place.Longitude}}/> )) } return null; },[showPlaces, places, icon]); // 补全places和icon const renderPlaces2Markers = useCallback(() => { console.log('rendering places2') if(showPlaces2){ return places2.map(place => ( <Marker key={place.ID} title={place.ID} position={{lat:place.Latitude,lng:place.Longitude}}/> )) } return null; },[showPlaces2, places2]); // 补全places2 const renderHeatMap = useCallback(() => { console.log('rendering heat map') if(showHeatMap){ const mapData = heatMap.map(location => ({ location: new google.maps.LatLng(location.Latitude,location.Longitude), weight: location.population_density })) return (<HeatmapLayer data={mapData} options={{gradient: gradient}} />) } return null; },[showHeatMap, heatMap, gradient]); // 补全heatMap和gradient
第二步:用memo组件替代渲染函数(核心优化)
直接调用缓存函数无法避免生成新JSX,更好的方式是把渲染逻辑封装成独立的memo组件,让React自动对比props来决定是否重新渲染:
// 在Map组件外部定义(或内部,建议外部避免重复创建) const PlacesMarkers = React.memo(({ show, places, icon }) => { console.log('rendering places'); if (!show) return null; return places.map(place => ( <Marker icon={icon} key={place.ID} title={place.Name} position={{ lat: place.Latitude, lng: place.Longitude }} /> )); }); const Places2Markers = React.memo(({ show, places }) => { console.log('rendering places2'); if (!show) return null; return places.map(place => ( <Marker key={place.ID} title={place.ID} position={{ lat: place.Latitude, lng: place.Longitude }} /> )); }); const HeatMapLayerWrapper = React.memo(({ show, heatMap, gradient }) => { console.log('rendering heat map'); if (!show) return null; const mapData = heatMap.map(location => ({ location: new google.maps.LatLng(location.Latitude, location.Longitude), weight: location.population_density })); return <HeatmapLayer data={mapData} options={{ gradient }} />; });
然后在Map组件的GoogleMap中使用这些组件:
<GoogleMap mapContainerStyle={containerStyle} center={center} zoom={10} onLoad={onLoad} onUnmount={onUnmount}> <PlacesMarkers show={showPlaces} places={places} icon={icon} /> <Places2Markers show={showPlaces2} places={places2} /> <HeatMapLayerWrapper show={showHeatMap} heatMap={heatMap} gradient={gradient} /> </GoogleMap>
第三步:优化Marker组件(可选但推荐)
如果使用的Marker组件没有自带memo优化,你可以自己包裹一层:
const MemoizedMarker = React.memo(Marker);
然后在上面的PlacesMarkers等组件中使用MemoizedMarker代替Marker,这样单个标记只有当props(比如位置、标题)变化时才会重新渲染。
为什么这样有效?
React.memo会对组件的props做浅比较,只有当props(比如showPlaces、places)变化时,才会重新渲染组件并生成新的JSX- 避免了每次
Map组件渲染时都执行渲染函数,只有必要时才会更新标记
内容的提问来源于stack exchange,提问作者Michael
相关产品推荐
相关产品推荐

