React.memo 不适用于功能组件和谷歌地图

Posted

技术标签:

【中文标题】React.memo 不适用于功能组件和谷歌地图【英文标题】:React.memo not working with functoinal components and google maps 【发布时间】:2021-10-12 06:07:33 【问题描述】:

我正在尝试在 React 应用程序中插入谷歌地图。我宁愿不使用非官方的库(我发现那些缺少文档的库),而且我已经设法插入了地图。

我的问题是每次父组件的状态改变时都会重新渲染地图;尽管改变的值与地图需要的完全无关。

经过一番研究(我是 React 新手),我发现了 React.memo() HOC,它应该可以防止子组件在其 props 未更改时重新渲染。但是由于某种原因,我无法让它正常工作。当我将地图插入没有道具的组件中时发生的事件,父状态的任何更改都会导致重新渲染地图。

这里是父组件:

const CompanyDepotsPopup = () => 
const classes = useStyles();
const dispatch = useDispatch();
const open = useSelector((state) => selectIsDepotsPopupOpen(state));
const company = useSelector((state) => selectSelectedCompany(state));
const depotsStatus = useSelector((state) => selectDepotsStatus(state));
const t = useTranslation();
const [value, setValue] = useState(0);
const [phone, setPhone] = useState("");

const handleChange = (event, newValue) => 
    setValue(newValue);
;

const closeModal = () => 
    dispatch(toggleDepotsPopup());


useEffect(() => 
    if (company) 
        dispatch(depotsListed(companyId: company.id));
    
, [company])

if (!company) return <></>;

if (depotsStatus === "loading") 
    return <CenteredLoader/>


function TabPanel(props) 
    const children, value, index = props;
    return (
        <div
            hidden=value !== index
            style=height: "100%"
        >
            value === index && (
                <Box boxShadow=3 mt=1 ml=2 mr=2 height="100%">
                    children
                </Box>
            )
        </div>
    );


return (
    <Dialog fullWidth=true open=open
            aria-labelledby="company-dialog-popup">
        <DialogTitle >
            company.name
        </DialogTitle>
        <DialogContent style=padding: 0, margin: 0>
            <Divider/>
            <Box mr=0 ml=0 mt=0 p=0 >
                <div >
                    <AppBar position="static">
                        <Tabs value=value onChange=handleChange aria-label="depots tabs" centered>
                            <Tab label=t("Company's depots list")/>
                            <Tab label=t("Add new depot")/>
                        </Tabs>
                    </AppBar>
                    <TabPanel value=value index=0>
                        <DepotsList/>
                    </TabPanel>
                    <TabPanel value=value index=1>
                        <Paper>
                            <Grid container spacing=2>
                                <Grid item xs=12 sm=12 md=12 lg=12>
                                    <TextField
                                        onChange=(event) => setPhone(event.target.value)
                                        id="phone"
                                        label=t("Phone")
                                        type="text"
                                        fullWidth
                                        value=phone
                                    />
                                </Grid>
                                <Grid item xs=12 sm=12 md=12 lg=12>
                                    <div style=height: "250px", display: "flex", "flexDirection": "column">
                                        <MyMap
                                            id="myMap"
                                        />
                                    </div>
                                </Grid>
                                <Grid item xs=12 sm=12 md=12 lg=12 align="center">
                                    <Button variant="outlined">
                                        t("Save")
                                    </Button>
                                </Grid>
                            </Grid>
                        </Paper>
                    </TabPanel>
                </div>
            </Box>
        </DialogContent>
        <DialogActions style=marginTop: "20px">
            <Button
                variant="outlined"
                onClick=closeModal
                color="secondary"
            >
                Done
            </Button>
        </DialogActions>
    </Dialog>
)

这是地图组件:

import React, useEffect from "react";

const Map = (id) => 
    const onScriptLoad = () => 
        const map = new window.google.maps.Map(
            document.getElementById(id),
            
                center: lat: 41.0082, lng: 28.9784,
                zoom: 8
            
        );
        const marker = new window.google.maps.Marker(
            position: lat: 41.0082, lng: 28.9784,
            map: map,
            title: 'Hello Istanbul!'
        );
    

    useEffect(() => 
        if (!window.google) 
            const s = document.createElement("script");
            s.type = "text/javascript";
            s.src = "https://maps.google.com/maps/api/js?key=''"
            const x = document.getElementsByTagName('script')[0];
            x.parentNode.insertBefore(s, x);
            s.addEventListener('load', e => 
                onScriptLoad();
            )
         else 
            onScriptLoad();
        
    , []);

    return (
        <div style=width: "100%", height: "100%" id=id/>
    );


const MyMap = React.memo(Map);
export default MyMap;

当用户键入电话并且状态发生变化时,每次调用 setPhone 时,都会重新渲染地图。有人可以向我解释为什么 React.memo 不起作用以及我应该如何进行以避免重新渲染地图?

【问题讨论】:

这很奇怪,我们在Map的第一行下面放一个console.log,在useEffect的第一行下面放一个。如果两个打印输出都显示多个渲染,那就证实了你所说的。 我意识到我无法真正回答你的问题,但我创建了一个codesandbox,也许可以帮助你更好地理解React.memo 【参考方案1】:

我觉得我的直觉是这个组件

function TabPanel(props) 
    const children, value, index = props;
    return (
        <div
            hidden=value !== index
            style=height: "100%"
        >
            value === index && (
                <Box boxShadow=3 mt=1 ml=2 mr=2 height="100%">
                    children
                </Box>
            )
        </div>
    );

这是在组件内部定义的,因此该组件的实例在任何状态更改后都会不断变化。为了防止它,把它移到组件之外,像这样

  function TabPanel()
  function CompanyDepotsPopup()

代替

  function CompanyDepotsPopup() 
    function TabPanel()
  

原因也是你的TabPanel 包装了其他所有内容。

【讨论】:

你说得对,就是这样。这当然是有道理的,只是我从未想过。谢谢! 不客气,我也摸不着头脑,因为您的大部分代码都不会导致任何重新渲染,尤其是 MyMap :) 很高兴这有帮助。

以上是关于React.memo 不适用于功能组件和谷歌地图的主要内容,如果未能解决你的问题,请参考以下文章

navigator.Geolocation GetCurrentpositon 不适用于 Chrome 和 Firefox

谷歌地图不适用于 ionic cordova run android

谷歌地图风格不适用于韩国地区

使用 react-redux、reselect 和 React.memo() 来记忆功能组件

底页不适用于谷歌地图

Android 模拟位置不适用于谷歌地图