微前端下无法访问sidebar,如何监听其clientWidth变化并获取?
你的代码里有两个核心问题导致拿不到sidebar宽度:
document是全局对象,永远不会变化,所以useEffect只会执行一次,要是执行时sidebar还没渲染(微前端异步加载的特性),自然拿不到值- 没有监听sidebar宽度的动态变化,就算第一次拿到值,后续sidebar宽度改变也无法捕获
下面给几个实用的解决办法:
方案一:用ResizeObserver+MutationObserver组合监听
先通过MutationObserver等待sidebar元素出现,再用ResizeObserver监听它的宽度变化,完美适配微前端异步加载的场景:
import { Box, BoxProps, HStack } from '@chakra-ui/react'; import React, { useEffect, useState } from 'react'; interface Props extends BoxProps { leftActions?: React.ReactNode[]; rightActions?: React.ReactNode[]; } export const FormFooter = ({ leftActions, rightActions, ...props }: Props) => { const [sidebarWidth, setSidebarWidth] = useState(0); useEffect(() => { let resizeObserver: ResizeObserver | null = null; const sidebarSelector = '.css-ygpt8u'; // 监听元素是否出现 const mutationObserver = new MutationObserver(() => { const sidebarEl = document.querySelector(sidebarSelector); if (sidebarEl && !resizeObserver) { // 元素出现后,监听宽度变化 resizeObserver = new ResizeObserver(entries => { const width = entries[0].contentRect.width; setSidebarWidth(width); console.log('sidebarWidth updated:', width); }); resizeObserver.observe(sidebarEl); // 先获取一次初始宽度 setSidebarWidth((sidebarEl as HTMLElement).clientWidth); } }); // 监听整个文档的DOM变化 mutationObserver.observe(document.body, { childList: true, subtree: true, }); return () => { mutationObserver.disconnect(); resizeObserver?.disconnect(); }; }, []); return ( <Box display={'flex'} justifyContent={'space-between'} {...props} width={`calc(100vw - ${sidebarWidth + 84}px)`} // 用动态宽度替换固定值 backgroundColor="white" > <HStack key="form-left">{leftActions?.map((element) => element)}</HStack> <HStack key="form-right">{rightActions?.map((element) => element)}</HStack> </Box> ); };
方案二:利用微前端通信传递宽度(最优解)
既然是微前端架构,sidebar所在的子应用可以主动把宽度变化通知给当前应用,避免DOM查询的耦合:
- 在sidebar所在的子应用中,监听自身宽度变化并发送自定义事件:
// sidebar子应用代码 const sidebarEl = document.querySelector('.css-ygpt8u'); if (sidebarEl) { const resizeObserver = new ResizeObserver(entries => { const width = entries[0].contentRect.width; // 发送自定义事件传递宽度 window.dispatchEvent(new CustomEvent('sidebar-width-change', { detail: { width } })); }); resizeObserver.observe(sidebarEl); // 初始发送一次宽度值 window.dispatchEvent(new CustomEvent('sidebar-width-change', { detail: { width: sidebarEl.clientWidth } })); }
- 在当前应用的
FormFooter组件中监听这个事件:
import { Box, BoxProps, HStack } from '@chakra-ui/react'; import React, { useEffect, useState } from 'react'; interface Props extends BoxProps { leftActions?: React.ReactNode[]; rightActions?: React.ReactNode[]; } export const FormFooter = ({ leftActions, rightActions, ...props }: Props) => { const [sidebarWidth, setSidebarWidth] = useState(0); useEffect(() => { const handleSidebarWidthChange = (e: CustomEvent) => { const newWidth = e.detail.width; setSidebarWidth(newWidth); console.log('sidebarWidth updated:', newWidth); }; window.addEventListener('sidebar-width-change', handleSidebarWidthChange as EventListener); // 组件卸载时移除监听 return () => { window.removeEventListener('sidebar-width-change', handleSidebarWidthChange as EventListener); }; }, []); return ( <Box display={'flex'} justifyContent={'space-between'} {...props} width={`calc(100vw - ${sidebarWidth + 84}px)`} backgroundColor="white" > <HStack key="form-left">{leftActions?.map((element) => element)}</HStack> <HStack key="form-right">{rightActions?.map((element) => element)}</HStack> </Box> ); };
方案三:定时轮询(兜底方案)
如果上面两种方案都没法实现,可以用定时轮询兜底,虽然不够优雅但简单有效:
import { Box, BoxProps, HStack } from '@chakra-ui/react'; import React, { useEffect, useState } from 'react'; interface Props extends BoxProps { leftActions?: React.ReactNode[]; rightActions?: React.ReactNode[]; } export const FormFooter = ({ leftActions, rightActions, ...props }: Props) => { const [sidebarWidth, setSidebarWidth] = useState(0); useEffect(() => { const intervalId = setInterval(() => { const sidebarEl = document.querySelector<HTMLElement>('.css-ygpt8u'); if (sidebarEl) { const newWidth = sidebarEl.clientWidth; if (newWidth !== sidebarWidth) { setSidebarWidth(newWidth); console.log('sidebarWidth updated:', newWidth); } } }, 300); // 每隔300ms查询一次,可按需调整 return () => clearInterval(intervalId); }, [sidebarWidth]); return ( <Box display={'flex'} justifyContent={'space-between'} {...props} width={`calc(100vw - ${sidebarWidth + 84}px)`} backgroundColor="white" > <HStack key="form-left">{leftActions?.map((element) => element)}</HStack> <HStack key="form-right">{rightActions?.map((element) => element)}</HStack> </Box> ); };
注意事项
- 尽量避免用
css-ygpt8u这种动态生成的样式类,微前端不同环境下可能会变化,最好让sidebar暴露固定自定义属性(比如data-sidebar),用[data-sidebar]查询更可靠 - 方案二是最优解,完全解耦了两个微应用,符合微前端架构设计原则
内容的提问来源于stack exchange,提问作者Nasir
相关产品推荐
相关产品推荐

