【问题标题】:React useCallback: useCallback is still re-rendering function each timeReact useCallback:useCallback 每次仍然是重新渲染函数
【发布时间】:2021-09-19 21:28:42
【问题描述】:

我对 react 中的 useMemouseCallback 钩子相当陌生,我有点不明白为什么即使我检查的变量保持不变,我的函数仍然会继续重新渲染.目前我正在尝试构建一个谷歌地图,允许用户打开和关闭地图上显示的某些标记。我的功能组件应该只需要在加载时渲染这些标记,然后记住它们,因为之后它们只是被打开/关闭。现在我理解了诸如此类的钩子仅进行浅层比较的概念,但我正在比较我认为这些钩子能够理解的布尔值。

这是我当前的地图文件:

// constants are above

function Map() {

    const {isLoaded} = useJsApiLoader({
        googleMapsApiKey: env.GOOGLE_MAPS_API.API_KEY,
        libraries: libs
    }) 

    const [map, setMap] = useState(null);
    const [places,setPlaces] = useState([]);
    const [places2,setPlaces2] = useState([]);
    const [heatMap,setHeatMap] = useState([]);
    const [fetchingData, setFetchingData] = useState(true);
    const [showPlaces,setShowPlaces] = useState(true);
    const [showPlaces2,setShowPlaces2] = useState(false);
    const [showHeatMap,setShowHeatMap] = useState(false);

    useEffect(() => {
        let mounted = true;

        if(mounted){
            socket.emit('getMapAnalytics', ([heatmap,places,places2]) => {
                console.log({heatmap})
                setPlaces(places);
                setPlaces2(places2);
                setHeatMap(heatmap);
                setFetchingData(false);
            });            
        }

        return () => {
            mounted = false;
        }
    },[])

    const onLoad = useCallback((map) => {
        setMap(map);
    },[])

    const onUnmount = useCallback((map) => {
        setMap(null);
    },[]);

    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]);

    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]);

    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]);

    const onChecked = (e) => {
        switch(e.target.id){
            case 'places': setShowPlaces(e.target.checked);
                break;
            case 'places2': setShowPlaces2(e.target.checked);
                break;
            case 'heat-map': setShowHeatMap(e.target.checked);
                break;
        }
    }

    return isLoaded && !fetchingData ? (
        <div id="map-container">
            <div id="top-bar" style={{display: 'flex', justifyContent:'space-evenly'}}>
                <div style={{display: 'flex'}}>
                    <input type="checkbox" id="places" checked={showPlaces} onChange={onChecked}></input><label>techs</label>
                </div>
                <div style={{display: 'flex'}}>
                    <input type="checkbox" id="places2" checked={showPlaces2} onChange={onChecked}></input><label>kiosks</label>
                </div>
                <div style={{display: 'flex'}}>
                    <input type="checkbox" id="heat-map" checked={showHeatMap} onChange={onChecked}></input><label>heatmap</label>
                </div>
            </div>
            <GoogleMap mapContainerStyle={containerStyle} center={center} zoom={10} onLoad={onLoad} onUnmount={onUnmount}>
                {renderPlacesMarkers()}
                {renderPlaces2Markers()}
                {renderHeatMap()}
            </GoogleMap>
        </div>        
    ) : <Spinner margin="margin-25"></Spinner>
}

export default memo(Map);

我尝试将 useCallback 参数设置为其他值,例如地点和地点2 的数组长度,但最终仍然会重新渲染每个函数。

我唯一能想到的就是让这些方法每次都重新渲染,因为我要返回一个 JSX 元素数组,但是我在网上找不到很好的例子。如果有人能指出我正确的方向以正确理解为什么会发生这种情况,那就太好了。蒂亚!

【问题讨论】:

  • 你可能有错字renderPlaces2Markers()--&gt;renderPlaces2()
  • @giorgimoniava 抱歉,发这篇文章时打错了。我已经更新了。

标签: reactjs react-hooks usecallback


【解决方案1】:

看来你误会了useCallback

如果你useCallback 某个函数,如果依赖关系没有改变,它就不会重新创建。但如果您手动调用它,它将始终运行

当您单击任何复选框时,地图会重新呈现。如果 renderPlaces 的依赖项没有更改,则函数将保持不变,但之后您仍要在渲染中手动调用它:renderPlaces()

根据您的设置,我的建议是将renderPlaces 和其他两个函数作为普通组件(并在Map 组件之外定义它们)并在它们上应用React.memo

一般来说,我会建议渲染组件而不是调用它们,例如最好使用&lt;Component/&gt; 而不是Component()。在功能组件内部定义功能组件是一个坏主意,因为反应协调算法会认为它们在每次渲染时都是不同的类型(因为如果您在函数内部定义函数,每次都会重新创建它)。

【讨论】:

  • 是的,所以renderPlacesMarkers, renderPlaces2Markers, &amp; renderHeatMap 方法只是在打开/关闭标记时在地图上显示标记。由于这些标记每次都是静态的,因此我不想通过数组重复以再次将它们显示在同一个地方。如果这有意义的话。
  • 所以我说得对,你是说为每个函数创建单独的组件会更好?
  • @Michael 是的,您可以在它们上应用React.memo,这样如果道具没有改变,它们就不会重新渲染。尝试理解我在答案中所说的所有内容,因为还提到了其他几点。
  • 感谢您的反馈。这应该会改善我在应用程序中渲染 React 组件的方式。我会试试这个。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-03-04
  • 1970-01-01
  • 2023-03-28
  • 1970-01-01
  • 1970-01-01
  • 2021-09-19
  • 2020-08-15
相关资源
最近更新 更多