【问题标题】:React.memo not working with functoinal components and google mapsReact.memo 不适用于功能组件和谷歌地图
【发布时间】: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} 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;

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

【问题讨论】:

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

标签: javascript reactjs react-hooks jsx memoization


【解决方案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 :) 很高兴这有帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-07
  • 1970-01-01
相关资源
最近更新 更多