React.memo 无法使用功能组件和 google 地图

React.memo not working with functoinal components and google maps

我正在尝试在 React 应用程序中插入 google 地图。我宁愿不使用非官方图书馆(我发现那些缺乏文档的图书馆)并且我已经设法插入地图。

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

经过一些研究(我是 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} height="95%">
                <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;

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

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

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 包裹了其他所有东西。